Completed
Branch models-cleanup/model-relations (f59c0d)
by
unknown
71:29 queued 62:36
created
core/db_models/EEM_Base.model.php 3 patches
Doc Comments   +19 added lines, -17 removed lines patch added patch discarded remove patch
@@ -916,7 +916,7 @@  discard block
 block discarded – undo
916 916
      *  on this model (or follows the _model_chain_to_wp_user and uses that model's
917 917
      * foreign key to the WP_User table)
918 918
      *
919
-     * @return string|boolean string on success, boolean false when there is no
919
+     * @return string|false string on success, boolean false when there is no
920 920
      * foreign key to the WP_User table
921 921
      * @throws ReflectionException
922 922
      */
@@ -1046,7 +1046,7 @@  discard block
 block discarded – undo
1046 1046
      *
1047 1047
      * @param array  $query_params
1048 1048
      * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1049
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1049
+     * @param string  $columns_to_select , What columns to select. By default, we select all columns specified by the
1050 1050
      *                                  fields on the model, and the models we joined to in the query. However, you can
1051 1051
      *                                  override this and set the select to "*", or a specific column name, like
1052 1052
      *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
@@ -1213,7 +1213,7 @@  discard block
 block discarded – undo
1213 1213
      * found in the database matching the given query conditions.
1214 1214
      *
1215 1215
      * @param mixed $current_field_value    Value used for the reference point.
1216
-     * @param null  $field_to_order_by      What field is used for the
1216
+     * @param string  $field_to_order_by      What field is used for the
1217 1217
      *                                      reference point.
1218 1218
      * @param int   $limit                  How many to return.
1219 1219
      * @param array $query_params           Extra conditions on the query.
@@ -1248,7 +1248,7 @@  discard block
 block discarded – undo
1248 1248
      * as found in the database matching the given query conditions.
1249 1249
      *
1250 1250
      * @param mixed $current_field_value    Value used for the reference point.
1251
-     * @param null  $field_to_order_by      What field is used for the
1251
+     * @param string  $field_to_order_by      What field is used for the
1252 1252
      *                                      reference point.
1253 1253
      * @param int   $limit                  How many to return.
1254 1254
      * @param array $query_params           Extra conditions on the query.
@@ -1283,7 +1283,7 @@  discard block
 block discarded – undo
1283 1283
      * database matching the given query conditions.
1284 1284
      *
1285 1285
      * @param mixed $current_field_value    Value used for the reference point.
1286
-     * @param null  $field_to_order_by      What field is used for the
1286
+     * @param string  $field_to_order_by      What field is used for the
1287 1287
      *                                      reference point.
1288 1288
      * @param array $query_params           Extra conditions on the query.
1289 1289
      * @param null  $columns_to_select      If left null, then an EE_Base_Class
@@ -1318,7 +1318,7 @@  discard block
 block discarded – undo
1318 1318
      * the database matching the given query conditions.
1319 1319
      *
1320 1320
      * @param mixed $current_field_value    Value used for the reference point.
1321
-     * @param null  $field_to_order_by      What field is used for the
1321
+     * @param string  $field_to_order_by      What field is used for the
1322 1322
      *                                      reference point.
1323 1323
      * @param array $query_params           Extra conditions on the query.
1324 1324
      * @param null  $columns_to_select      If left null, then an EE_Base_Class
@@ -1470,7 +1470,7 @@  discard block
 block discarded – undo
1470 1470
      *
1471 1471
      * @param string $field_name The name of the field the formats are being retrieved for.
1472 1472
      * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1473
-     * @return array formats in an array with the date format first, and the time format last.
1473
+     * @return string[] formats in an array with the date format first, and the time format last.
1474 1474
      * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1475 1475
      * @since 4.6.x
1476 1476
      */
@@ -1551,7 +1551,7 @@  discard block
 block discarded – undo
1551 1551
      *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1552 1552
      *                                format is
1553 1553
      *                                'U', this is ignored.
1554
-     * @return DateTime
1554
+     * @return string
1555 1555
      * @throws EE_Error
1556 1556
      */
1557 1557
     public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
@@ -2476,7 +2476,7 @@  discard block
 block discarded – undo
2476 2476
      * Verifies the EE addons' database is up-to-date and records that we've done it on
2477 2477
      * EEM_Base::$_db_verification_level
2478 2478
      *
2479
-     * @param $wpdb_method
2479
+     * @param string $wpdb_method
2480 2480
      * @param $arguments_to_provide
2481 2481
      * @return string
2482 2482
      * @throws EE_Error
@@ -2595,6 +2595,7 @@  discard block
 block discarded – undo
2595 2595
      *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2596 2596
      *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2597 2597
      *                             because these will be inserted in any new rows created as well.
2598
+     * @param EE_Base_Class $id_or_obj
2598 2599
      * @return boolean of success
2599 2600
      * @throws EE_Error
2600 2601
      */
@@ -2606,7 +2607,7 @@  discard block
 block discarded – undo
2606 2607
 
2607 2608
 
2608 2609
     /**
2609
-     * @param mixed  $id_or_obj
2610
+     * @param EE_Base_Class  $id_or_obj
2610 2611
      * @param string $relationName
2611 2612
      * @param array  $where_query_params
2612 2613
      * @param EE_Base_Class[] objects to which relations were removed
@@ -2647,7 +2648,7 @@  discard block
 block discarded – undo
2647 2648
      * However, if the model objects can't be deleted because of blocking related model objects, then
2648 2649
      * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2649 2650
      *
2650
-     * @param EE_Base_Class|int|string $id_or_obj
2651
+     * @param EE_Base_Class $id_or_obj
2651 2652
      * @param string                   $model_name
2652 2653
      * @param array                    $query_params
2653 2654
      * @return int how many deleted
@@ -2668,7 +2669,7 @@  discard block
 block discarded – undo
2668 2669
      * the model objects can't be hard deleted because of blocking related model objects,
2669 2670
      * just does a soft-delete on them instead.
2670 2671
      *
2671
-     * @param EE_Base_Class|int|string $id_or_obj
2672
+     * @param EE_Base_Class $id_or_obj
2672 2673
      * @param string                   $model_name
2673 2674
      * @param array                    $query_params
2674 2675
      * @return int how many deleted
@@ -2726,6 +2727,7 @@  discard block
 block discarded – undo
2726 2727
      * @param string $model_name   like 'Event', or 'Registration'
2727 2728
      * @param array  $query_params
2728 2729
      * @param string $field_to_sum name of field to count by. By default, uses primary key
2730
+     * @param EE_Base_Class $id_or_obj
2729 2731
      * @return float
2730 2732
      * @throws EE_Error
2731 2733
      * @throws ReflectionException
@@ -3759,7 +3761,7 @@  discard block
 block discarded – undo
3759 3761
      * We should use default where conditions on related models when they requested to use default where conditions
3760 3762
      * on all models, or specifically just on other related models
3761 3763
      *
3762
-     * @param      $default_where_conditions_value
3764
+     * @param      string $default_where_conditions_value
3763 3765
      * @param bool $for_this_model false means this is for OTHER related models
3764 3766
      * @return bool
3765 3767
      */
@@ -3799,7 +3801,7 @@  discard block
 block discarded – undo
3799 3801
      * We should use minimum where conditions on related models if they requested to use minimum where conditions
3800 3802
      * on this model or others
3801 3803
      *
3802
-     * @param      $default_where_conditions_value
3804
+     * @param      string $default_where_conditions_value
3803 3805
      * @param bool $for_this_model false means this is for OTHER related models
3804 3806
      * @return bool
3805 3807
      */
@@ -4988,7 +4990,7 @@  discard block
 block discarded – undo
4988 4990
      * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4989 4991
      * Eg, on EE_Answer that would be ANS_ID field object
4990 4992
      *
4991
-     * @param $field_obj
4993
+     * @param EE_Model_Field_Base $field_obj
4992 4994
      * @return boolean
4993 4995
      */
4994 4996
     public function is_primary_key_field($field_obj)
@@ -5798,7 +5800,7 @@  discard block
 block discarded – undo
5798 5800
     /**
5799 5801
      * Read comments for assume_values_already_prepared_by_model_object()
5800 5802
      *
5801
-     * @return int
5803
+     * @return boolean
5802 5804
      */
5803 5805
     public function get_assumption_concerning_values_already_prepared_by_model_object()
5804 5806
     {
@@ -6434,7 +6436,7 @@  discard block
 block discarded – undo
6434 6436
     /**
6435 6437
      * Returns the password field on this model, if there is one
6436 6438
      *
6437
-     * @return EE_Password_Field|null
6439
+     * @return EE_Model_Field_Base|null
6438 6440
      * @since 4.9.74.p
6439 6441
      */
6440 6442
     public function getPasswordField()
Please login to merge, or discard this patch.
Indentation   +6547 added lines, -6547 removed lines patch added patch discarded remove patch
@@ -37,6553 +37,6553 @@
 block discarded – undo
37 37
 abstract class EEM_Base extends EE_Base implements ResettableInterface
38 38
 {
39 39
 
40
-    /**
41
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
42
-     */
43
-    const caps_read       = 'read';
44
-
45
-    const caps_read_admin = 'read_admin';
46
-
47
-    const caps_edit       = 'edit';
48
-
49
-    const caps_delete     = 'delete';
50
-
51
-
52
-    /**
53
-     * constant used to show EEM_Base has not yet verified the db on this http request
54
-     */
55
-    const db_verified_none = 0;
56
-
57
-    /**
58
-     * constant used to show EEM_Base has verified the EE core db on this http request,
59
-     * but not the addons' dbs
60
-     */
61
-    const db_verified_core = 1;
62
-
63
-    /**
64
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
65
-     * the EE core db too)
66
-     */
67
-    const db_verified_addons = 2;
68
-
69
-    /**
70
-     * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
71
-     *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
72
-     *        registrations for non-trashed tickets for non-trashed datetimes)
73
-     */
74
-    const default_where_conditions_all = 'all';
75
-
76
-    /**
77
-     * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
78
-     *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
79
-     *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
80
-     *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
81
-     *        models which share tables with other models, this can return data for the wrong model.
82
-     */
83
-    const default_where_conditions_this_only = 'this_model_only';
84
-
85
-    /**
86
-     * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
87
-     *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
88
-     *        return all registrations related to non-trashed tickets and non-trashed datetimes)
89
-     */
90
-    const default_where_conditions_others_only = 'other_models_only';
91
-
92
-    /**
93
-     * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
94
-     *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
95
-     *        their table with other models, like the Event and Venue models. For example, when querying for events
96
-     *        ordered by their venues' name, this will be sure to only return real events with associated real venues
97
-     *        (regardless of whether those events and venues are trashed)
98
-     *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
99
-     *        events.
100
-     */
101
-    const default_where_conditions_minimum_all = 'minimum';
102
-
103
-    /**
104
-     * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
105
-     *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
106
-     *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
107
-     *        not)
108
-     */
109
-    const default_where_conditions_minimum_others = 'full_this_minimum_others';
110
-
111
-    /**
112
-     * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
113
-     *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
114
-     *        it's possible it will return table entries for other models. You should use
115
-     *        EEM_Base::default_where_conditions_minimum_all instead.
116
-     */
117
-    const default_where_conditions_none = 'none';
118
-
119
-    /**
120
-     * when $_values_already_prepared_by_model_object equals this, we assume
121
-     * the data is just like form input that needs to have the model fields'
122
-     * prepare_for_set and prepare_for_use_in_db called on it
123
-     */
124
-    const not_prepared_by_model_object = 0;
125
-
126
-    /**
127
-     * when $_values_already_prepared_by_model_object equals this, we
128
-     * assume this value is coming from a model object and doesn't need to have
129
-     * prepare_for_set called on it, just prepare_for_use_in_db is used
130
-     */
131
-    const prepared_by_model_object = 1;
132
-
133
-    /**
134
-     * when $_values_already_prepared_by_model_object equals this, we assume
135
-     * the values are already to be used in the database (ie no processing is done
136
-     * on them by the model's fields)
137
-     */
138
-    const prepared_for_use_in_db = 2;
139
-
140
-    /**
141
-     * Flag to indicate whether the values provided to EEM_Base have already been prepared
142
-     * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
143
-     * They almost always WILL NOT, but it's not necessarily a requirement.
144
-     * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
145
-     *
146
-     * @var boolean
147
-     */
148
-    private $_values_already_prepared_by_model_object = 0;
149
-
150
-
151
-    /**
152
-     * @var string
153
-     */
154
-    protected $singular_item = 'Item';
155
-
156
-    /**
157
-     * @var string
158
-     */
159
-    protected $plural_item = 'Items';
160
-
161
-    /**
162
-     * array of EE_Table objects for defining which tables comprise this model.
163
-     *
164
-     * @type EE_Table_Base[] $_tables
165
-     */
166
-    protected $_tables;
167
-
168
-    /**
169
-     * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
170
-     * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
171
-     * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
172
-     *
173
-     * @var EE_Model_Field_Base[][] $_fields
174
-     */
175
-    protected $_fields;
176
-
177
-    /**
178
-     * array of different kinds of relations
179
-     *
180
-     * @var EE_Model_Relation_Base[] $_model_relations
181
-     */
182
-    protected $_model_relations;
183
-
184
-    /**
185
-     * @var EE_Index[] $_indexes
186
-     */
187
-    protected $_indexes = [];
188
-
189
-    /**
190
-     * Default strategy for getting where conditions on this model. This strategy is used to get default
191
-     * where conditions which are added to get_all, update, and delete queries. They can be overridden
192
-     * by setting the same columns as used in these queries in the query yourself.
193
-     *
194
-     * @var EE_Default_Where_Conditions
195
-     */
196
-    protected $_default_where_conditions_strategy;
197
-
198
-    /**
199
-     * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
200
-     * This is particularly useful when you want something between 'none' and 'default'
201
-     *
202
-     * @var EE_Default_Where_Conditions
203
-     */
204
-    protected $_minimum_where_conditions_strategy;
205
-
206
-    /**
207
-     * String describing how to find the "owner" of this model's objects.
208
-     * When there is a foreign key on this model to the wp_users table, this isn't needed.
209
-     * But when there isn't, this indicates which related model, or transiently-related model,
210
-     * has the foreign key to the wp_users table.
211
-     * Eg, for EEM_Registration this would be 'Event' because registrations are directly
212
-     * related to events, and events have a foreign key to wp_users.
213
-     * On EEM_Transaction, this would be 'Transaction.Event'
214
-     *
215
-     * @var string
216
-     */
217
-    protected $_model_chain_to_wp_user = '';
218
-
219
-    /**
220
-     * String describing how to find the model with a password controlling access to this model. This property has the
221
-     * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
222
-     * This value is the path of models to follow to arrive at the model with the password field.
223
-     * If it is an empty string, it means this model has the password field. If it is null, it means there is no
224
-     * model with a password that should affect reading this on the front-end.
225
-     * Eg this is an empty string for the Event model because it has a password.
226
-     * This is null for the Registration model, because its event's password has no bearing on whether
227
-     * you can read the registration or not on the front-end (it just depends on your capabilities.)
228
-     * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
229
-     * should hide tickets for datetimes for events that have a password set.
230
-     *
231
-     * @var string |null
232
-     */
233
-    protected $model_chain_to_password = null;
234
-
235
-    /**
236
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
237
-     * don't need it (particularly CPT models)
238
-     *
239
-     * @var bool
240
-     */
241
-    protected $_ignore_where_strategy = false;
242
-
243
-    /**
244
-     * String used in caps relating to this model. Eg, if the caps relating to this
245
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
246
-     *
247
-     * @var string. If null it hasn't been initialized yet. If false then we
248
-     * have indicated capabilities don't apply to this
249
-     */
250
-    protected $_caps_slug = null;
251
-
252
-    /**
253
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
254
-     * and next-level keys are capability names, and values are a
255
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
256
-     * they specify which context to use (ie, frontend, backend, edit or delete)
257
-     * and then each capability in the corresponding sub-array that they're missing
258
-     * adds the where conditions onto the query.
259
-     *
260
-     * @var array
261
-     */
262
-    protected $_cap_restrictions = [
263
-        self::caps_read       => [],
264
-        self::caps_read_admin => [],
265
-        self::caps_edit       => [],
266
-        self::caps_delete     => [],
267
-    ];
268
-
269
-    /**
270
-     * Array defining which cap restriction generators to use to create default
271
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
272
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
273
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
274
-     * automatically set this to false (not just null).
275
-     *
276
-     * @var EE_Restriction_Generator_Base[]
277
-     */
278
-    protected $_cap_restriction_generators = [];
279
-
280
-    /**
281
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
282
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
283
-     * maps to 'read' because when looking for relevant permissions we're going to use
284
-     * 'read' in teh capabilities names like 'ee_read_events' etc.
285
-     *
286
-     * @var array
287
-     */
288
-    protected $_cap_contexts_to_cap_action_map = [
289
-        self::caps_read       => 'read',
290
-        self::caps_read_admin => 'read',
291
-        self::caps_edit       => 'edit',
292
-        self::caps_delete     => 'delete',
293
-    ];
294
-
295
-    /**
296
-     * Timezone
297
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
298
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
299
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
300
-     * EE_Datetime_Field data type will have access to it.
301
-     *
302
-     * @var string
303
-     */
304
-    protected $_timezone;
305
-
306
-
307
-    /**
308
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
309
-     * multisite.
310
-     *
311
-     * @var int
312
-     */
313
-    protected static $_model_query_blog_id;
314
-
315
-    /**
316
-     * A copy of _fields, except the array keys are the model names pointed to by
317
-     * the field
318
-     *
319
-     * @var EE_Model_Field_Base[]
320
-     */
321
-    private $_cache_foreign_key_to_fields = [];
322
-
323
-    /**
324
-     * Cached list of all the fields on the model, indexed by their name
325
-     *
326
-     * @var EE_Model_Field_Base[]
327
-     */
328
-    private $_cached_fields = null;
329
-
330
-    /**
331
-     * Cached list of all the fields on the model, except those that are
332
-     * marked as only pertinent to the database
333
-     *
334
-     * @var EE_Model_Field_Base[]
335
-     */
336
-    private $_cached_fields_non_db_only = null;
337
-
338
-    /**
339
-     * A cached reference to the primary key for quick lookup
340
-     *
341
-     * @var EE_Model_Field_Base
342
-     */
343
-    private $_primary_key_field = null;
344
-
345
-    /**
346
-     * Flag indicating whether this model has a primary key or not
347
-     *
348
-     * @var boolean
349
-     */
350
-    protected $_has_primary_key_field = null;
351
-
352
-    /**
353
-     * array in the format:  [ FK alias => full PK ]
354
-     * where keys are local column name aliases for foreign keys
355
-     * and values are the fully qualified column name for the primary key they represent
356
-     *  ex:
357
-     *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
358
-     *
359
-     * @var array $foreign_key_aliases
360
-     */
361
-    protected $foreign_key_aliases = [];
362
-
363
-    /**
364
-     * Whether or not this model is based off a table in WP core only (CPTs should set
365
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
366
-     * This should be true for models that deal with data that should exist independent of EE.
367
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
368
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
369
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
370
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
371
-     *
372
-     * @var boolean
373
-     */
374
-    protected $_wp_core_model = false;
375
-
376
-    /**
377
-     * @var bool stores whether this model has a password field or not.
378
-     * null until initialized by hasPasswordField()
379
-     */
380
-    protected $has_password_field;
381
-
382
-    /**
383
-     * @var EE_Password_Field|null Automatically set when calling getPasswordField()
384
-     */
385
-    protected $password_field;
386
-
387
-    /**
388
-     *    List of valid operators that can be used for querying.
389
-     * The keys are all operators we'll accept, the values are the real SQL
390
-     * operators used
391
-     *
392
-     * @var array
393
-     */
394
-    protected $_valid_operators = [
395
-        '='           => '=',
396
-        '<='          => '<=',
397
-        '<'           => '<',
398
-        '>='          => '>=',
399
-        '>'           => '>',
400
-        '!='          => '!=',
401
-        'LIKE'        => 'LIKE',
402
-        'like'        => 'LIKE',
403
-        'NOT_LIKE'    => 'NOT LIKE',
404
-        'not_like'    => 'NOT LIKE',
405
-        'NOT LIKE'    => 'NOT LIKE',
406
-        'not like'    => 'NOT LIKE',
407
-        'IN'          => 'IN',
408
-        'in'          => 'IN',
409
-        'NOT_IN'      => 'NOT IN',
410
-        'not_in'      => 'NOT IN',
411
-        'NOT IN'      => 'NOT IN',
412
-        'not in'      => 'NOT IN',
413
-        'between'     => 'BETWEEN',
414
-        'BETWEEN'     => 'BETWEEN',
415
-        'IS_NOT_NULL' => 'IS NOT NULL',
416
-        'is_not_null' => 'IS NOT NULL',
417
-        'IS NOT NULL' => 'IS NOT NULL',
418
-        'is not null' => 'IS NOT NULL',
419
-        'IS_NULL'     => 'IS NULL',
420
-        'is_null'     => 'IS NULL',
421
-        'IS NULL'     => 'IS NULL',
422
-        'is null'     => 'IS NULL',
423
-        'REGEXP'      => 'REGEXP',
424
-        'regexp'      => 'REGEXP',
425
-        'NOT_REGEXP'  => 'NOT REGEXP',
426
-        'not_regexp'  => 'NOT REGEXP',
427
-        'NOT REGEXP'  => 'NOT REGEXP',
428
-        'not regexp'  => 'NOT REGEXP',
429
-    ];
430
-
431
-    /**
432
-     * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
433
-     *
434
-     * @var array
435
-     */
436
-    protected $_in_style_operators = ['IN', 'NOT IN'];
437
-
438
-    /**
439
-     * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
440
-     * '12-31-2012'"
441
-     *
442
-     * @var array
443
-     */
444
-    protected $_between_style_operators = ['BETWEEN'];
445
-
446
-    /**
447
-     * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
448
-     *
449
-     * @var array
450
-     */
451
-    protected $_like_style_operators = ['LIKE', 'NOT LIKE'];
452
-
453
-    /**
454
-     * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
455
-     * on a join table.
456
-     *
457
-     * @var array
458
-     */
459
-    protected $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
460
-
461
-    /**
462
-     * Allowed values for $query_params['order'] for ordering in queries
463
-     *
464
-     * @var array
465
-     */
466
-    protected $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
467
-
468
-    /**
469
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
470
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
471
-     *
472
-     * @var array
473
-     */
474
-    private $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
475
-
476
-    /**
477
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
478
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
479
-     *
480
-     * @var array
481
-     */
482
-    private $_allowed_query_params = [
483
-        0,
484
-        'limit',
485
-        'order_by',
486
-        'group_by',
487
-        'having',
488
-        'force_join',
489
-        'order',
490
-        'on_join_limit',
491
-        'default_where_conditions',
492
-        'caps',
493
-        'extra_selects',
494
-        'exclude_protected',
495
-    ];
496
-
497
-    /**
498
-     * All the data types that can be used in $wpdb->prepare statements.
499
-     *
500
-     * @var array
501
-     */
502
-    private $_valid_wpdb_data_types = ['%d', '%s', '%f'];
503
-
504
-    /**
505
-     * @var EE_Registry $EE
506
-     */
507
-    protected $EE = null;
508
-
509
-
510
-    /**
511
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
512
-     *
513
-     * @var int
514
-     */
515
-    protected $_show_next_x_db_queries = 0;
516
-
517
-    /**
518
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
519
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
520
-     * WHERE, GROUP_BY, etc.
521
-     *
522
-     * @var CustomSelects
523
-     */
524
-    protected $_custom_selections = [];
525
-
526
-    /**
527
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
528
-     * caches every model object we've fetched from the DB on this request
529
-     *
530
-     * @var array
531
-     */
532
-    protected $_entity_map;
533
-
534
-    /**
535
-     * @var LoaderInterface $loader
536
-     */
537
-    private static $loader;
538
-
539
-    /**
540
-     * indicates whether an EEM_Base child has already re-verified the DB
541
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
542
-     * looking like EEM_Base::db_verified_*
543
-     *
544
-     * @var int - 0 = none, 1 = core, 2 = addons
545
-     */
546
-    protected static $_db_verification_level = EEM_Base::db_verified_none;
547
-
548
-
549
-    /**
550
-     * About all child constructors:
551
-     * they should define the _tables, _fields and _model_relations arrays.
552
-     * Should ALWAYS be called after child constructor.
553
-     * In order to make the child constructors to be as simple as possible, this parent constructor
554
-     * finalizes constructing all the object's attributes.
555
-     * Generally, rather than requiring a child to code
556
-     * $this->_tables = array(
557
-     *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
558
-     *        ...);
559
-     *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
560
-     * each EE_Table has a function to set the table's alias after the constructor, using
561
-     * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
562
-     * do something similar.
563
-     *
564
-     * @param null $timezone
565
-     * @throws EE_Error
566
-     */
567
-    protected function __construct($timezone = null)
568
-    {
569
-        // check that the model has not been loaded too soon
570
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
571
-            throw new EE_Error(
572
-                sprintf(
573
-                    esc_html__(
574
-                        'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
575
-                        'event_espresso'
576
-                    ),
577
-                    get_class($this)
578
-                )
579
-            );
580
-        }
581
-        /**
582
-         * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
583
-         */
584
-        if (empty(EEM_Base::$_model_query_blog_id)) {
585
-            EEM_Base::set_model_query_blog_id();
586
-        }
587
-        /**
588
-         * Filters the list of tables on a model. It is best to NOT use this directly and instead
589
-         * just use EE_Register_Model_Extension
590
-         *
591
-         * @var EE_Table_Base[] $_tables
592
-         */
593
-        $this->_tables = (array)apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
594
-        foreach ($this->_tables as $table_alias => $table_obj) {
595
-            /** @var $table_obj EE_Table_Base */
596
-            $table_obj->_construct_finalize_with_alias($table_alias);
597
-            if ($table_obj instanceof EE_Secondary_Table) {
598
-                /** @var $table_obj EE_Secondary_Table */
599
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
600
-            }
601
-        }
602
-        /**
603
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
604
-         * EE_Register_Model_Extension
605
-         *
606
-         * @param EE_Model_Field_Base[] $_fields
607
-         */
608
-        $this->_fields = (array)apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
609
-        $this->_invalidate_field_caches();
610
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
611
-            if (! array_key_exists($table_alias, $this->_tables)) {
612
-                throw new EE_Error(
613
-                    sprintf(
614
-                        esc_html__(
615
-                            "Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
616
-                            'event_espresso'
617
-                        ),
618
-                        $table_alias,
619
-                        implode(",", $this->_fields)
620
-                    )
621
-                );
622
-            }
623
-            foreach ($fields_for_table as $field_name => $field_obj) {
624
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
625
-                // primary key field base has a slightly different _construct_finalize
626
-                /** @var $field_obj EE_Model_Field_Base */
627
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
628
-            }
629
-        }
630
-        // everything is related to Extra_Meta
631
-        if (get_class($this) !== 'EEM_Extra_Meta') {
632
-            // make extra meta related to everything, but don't block deleting things just
633
-            // because they have related extra meta info. For now just orphan those extra meta
634
-            // in the future we should automatically delete them
635
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
636
-        }
637
-        // and change logs
638
-        if (get_class($this) !== 'EEM_Change_Log') {
639
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
640
-        }
641
-        /**
642
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
643
-         * EE_Register_Model_Extension
644
-         *
645
-         * @param EE_Model_Relation_Base[] $_model_relations
646
-         */
647
-        $this->_model_relations = (array)apply_filters(
648
-            'FHEE__' . get_class($this) . '__construct__model_relations',
649
-            $this->_model_relations
650
-        );
651
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
652
-            /** @var $relation_obj EE_Model_Relation_Base */
653
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
654
-        }
655
-        foreach ($this->_indexes as $index_name => $index_obj) {
656
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
657
-        }
658
-        $this->set_timezone($timezone);
659
-        // finalize default where condition strategy, or set default
660
-        if (! $this->_default_where_conditions_strategy) {
661
-            // nothing was set during child constructor, so set default
662
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
663
-        }
664
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
665
-        if (! $this->_minimum_where_conditions_strategy) {
666
-            // nothing was set during child constructor, so set default
667
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
668
-        }
669
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
670
-        // if the cap slug hasn't been set, and we haven't set it to false on purpose
671
-        // to indicate to NOT set it, set it to the logical default
672
-        if ($this->_caps_slug === null) {
673
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
674
-        }
675
-        // initialize the standard cap restriction generators if none were specified by the child constructor
676
-        if ($this->_cap_restriction_generators !== false) {
677
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
678
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
679
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
680
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
681
-                        new EE_Restriction_Generator_Protected(),
682
-                        $cap_context,
683
-                        $this
684
-                    );
685
-                }
686
-            }
687
-        }
688
-        // if there are cap restriction generators, use them to make the default cap restrictions
689
-        if ($this->_cap_restriction_generators !== false) {
690
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
691
-                if (! $generator_object) {
692
-                    continue;
693
-                }
694
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
695
-                    throw new EE_Error(
696
-                        sprintf(
697
-                            esc_html__(
698
-                                'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
699
-                                'event_espresso'
700
-                            ),
701
-                            $context,
702
-                            $this->get_this_model_name()
703
-                        )
704
-                    );
705
-                }
706
-                $action = $this->cap_action_for_context($context);
707
-                if (! $generator_object->construction_finalized()) {
708
-                    $generator_object->_construct_finalize($this, $action);
709
-                }
710
-            }
711
-        }
712
-        do_action('AHEE__' . get_class($this) . '__construct__end');
713
-    }
714
-
715
-
716
-    /**
717
-     * Used to set the $_model_query_blog_id static property.
718
-     *
719
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
720
-     *                      value for get_current_blog_id() will be used.
721
-     */
722
-    public static function set_model_query_blog_id($blog_id = 0)
723
-    {
724
-        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int)$blog_id : get_current_blog_id();
725
-    }
726
-
727
-
728
-    /**
729
-     * Returns whatever is set as the internal $model_query_blog_id.
730
-     *
731
-     * @return int
732
-     */
733
-    public static function get_model_query_blog_id()
734
-    {
735
-        return EEM_Base::$_model_query_blog_id;
736
-    }
737
-
738
-
739
-    /**
740
-     * This function is a singleton method used to instantiate the Espresso_model object
741
-     *
742
-     * @param string $timezone        string representing the timezone we want to set for returned Date Time Strings
743
-     *                                (and any incoming timezone data that gets saved).
744
-     *                                Note this just sends the timezone info to the date time model field objects.
745
-     *                                Default is NULL
746
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
747
-     * @return static (as in the concrete child class)
748
-     * @throws EE_Error
749
-     * @throws InvalidArgumentException
750
-     * @throws InvalidDataTypeException
751
-     * @throws InvalidInterfaceException
752
-     */
753
-    public static function instance($timezone = null)
754
-    {
755
-        // check if instance of Espresso_model already exists
756
-        if (! static::$_instance instanceof static) {
757
-            // instantiate Espresso_model
758
-            static::$_instance = new static(
759
-                $timezone,
760
-                LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
761
-            );
762
-        }
763
-        // we might have a timezone set, let set_timezone decide what to do with it
764
-        static::$_instance->set_timezone($timezone);
765
-        // Espresso_model object
766
-        return static::$_instance;
767
-    }
768
-
769
-
770
-    /**
771
-     * resets the model and returns it
772
-     *
773
-     * @param null | string $timezone
774
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
775
-     * all its properties reset; if it wasn't instantiated, returns null)
776
-     * @throws EE_Error
777
-     * @throws ReflectionException
778
-     * @throws InvalidArgumentException
779
-     * @throws InvalidDataTypeException
780
-     * @throws InvalidInterfaceException
781
-     */
782
-    public static function reset($timezone = null)
783
-    {
784
-        if (static::$_instance instanceof EEM_Base) {
785
-            // let's try to NOT swap out the current instance for a new one
786
-            // because if someone has a reference to it, we can't remove their reference
787
-            // so it's best to keep using the same reference, but change the original object
788
-            // reset all its properties to their original values as defined in the class
789
-            $r                 = new ReflectionClass(get_class(static::$_instance));
790
-            $static_properties = $r->getStaticProperties();
791
-            foreach ($r->getDefaultProperties() as $property => $value) {
792
-                // don't set instance to null like it was originally,
793
-                // but it's static anyways, and we're ignoring static properties (for now at least)
794
-                if (! isset($static_properties[ $property ])) {
795
-                    static::$_instance->{$property} = $value;
796
-                }
797
-            }
798
-            // and then directly call its constructor again, like we would if we were creating a new one
799
-            static::$_instance->__construct(
800
-                $timezone,
801
-                LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
802
-            );
803
-            return self::instance();
804
-        }
805
-        return null;
806
-    }
807
-
808
-
809
-    /**
810
-     * @return LoaderInterface
811
-     * @throws InvalidArgumentException
812
-     * @throws InvalidDataTypeException
813
-     * @throws InvalidInterfaceException
814
-     */
815
-    private static function getLoader()
816
-    {
817
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
818
-            EEM_Base::$loader = LoaderFactory::getLoader();
819
-        }
820
-        return EEM_Base::$loader;
821
-    }
822
-
823
-
824
-    /**
825
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
826
-     *
827
-     * @param boolean $translated return localized strings or JUST the array.
828
-     * @return array
829
-     * @throws EE_Error
830
-     * @throws InvalidArgumentException
831
-     * @throws InvalidDataTypeException
832
-     * @throws InvalidInterfaceException
833
-     * @throws ReflectionException
834
-     */
835
-    public function status_array($translated = false)
836
-    {
837
-        if (! array_key_exists('Status', $this->_model_relations)) {
838
-            return [];
839
-        }
840
-        $model_name   = $this->get_this_model_name();
841
-        $status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
842
-        $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
843
-        $status_array = [];
844
-        foreach ($stati as $status) {
845
-            $status_array[ $status->ID() ] = $status->get('STS_code');
846
-        }
847
-        return $translated
848
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
849
-            : $status_array;
850
-    }
851
-
852
-
853
-    /**
854
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
855
-     *
856
-     * @param array $query_params             see github link below for more info
857
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
858
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
859
-     *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
860
-     *                                        Some full examples: get 10 transactions which have Scottish attendees:
861
-     *                                        EEM_Transaction::instance()->get_all( array( array(
862
-     *                                        'OR'=>array(
863
-     *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
864
-     *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
865
-     *                                        )
866
-     *                                        ),
867
-     *                                        'limit'=>10,
868
-     *                                        'group_by'=>'TXN_ID'
869
-     *                                        ));
870
-     *                                        get all the answers to the question titled "shirt size" for event with id
871
-     *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
872
-     *                                        'Question.QST_display_text'=>'shirt size',
873
-     *                                        'Registration.Event.EVT_ID'=>12
874
-     *                                        ),
875
-     *                                        'order_by'=>array('ANS_value'=>'ASC')
876
-     *                                        ));
877
-     * @throws EE_Error
878
-     * @throws ReflectionException
879
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
880
-     *                                        or if you have the development copy of EE you can view this at the path:
881
-     *                                        /docs/G--Model-System/model-query-params.md
882
-     */
883
-    public function get_all($query_params = [])
884
-    {
885
-        if (
886
-            isset($query_params['limit'])
887
-            && ! isset($query_params['group_by'])
888
-        ) {
889
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
890
-        }
891
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
892
-    }
893
-
894
-
895
-    /**
896
-     * Modifies the query parameters so we only get back model objects
897
-     * that "belong" to the current user
898
-     *
899
-     * @param array $query_params see github link below for more info
900
-     * @return array
901
-     * @throws ReflectionException
902
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
903
-     */
904
-    public function alter_query_params_to_only_include_mine($query_params = [])
905
-    {
906
-        $wp_user_field_name = $this->wp_user_field_name();
907
-        if ($wp_user_field_name) {
908
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
909
-        }
910
-        return $query_params;
911
-    }
912
-
913
-
914
-    /**
915
-     * Returns the name of the field's name that points to the WP_User table
916
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
917
-     * foreign key to the WP_User table)
918
-     *
919
-     * @return string|boolean string on success, boolean false when there is no
920
-     * foreign key to the WP_User table
921
-     * @throws ReflectionException
922
-     */
923
-    public function wp_user_field_name()
924
-    {
925
-        try {
926
-            if (! empty($this->_model_chain_to_wp_user)) {
927
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
928
-                $last_model_name              = end($models_to_follow_to_wp_users);
929
-                $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
930
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
931
-            } else {
932
-                $model_with_fk_to_wp_users = $this;
933
-                $model_chain_to_wp_user    = '';
934
-            }
935
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
936
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
937
-        } catch (EE_Error $e) {
938
-            return false;
939
-        }
940
-    }
941
-
942
-
943
-    /**
944
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
945
-     * (or transiently-related model) has a foreign key to the wp_users table;
946
-     * useful for finding if model objects of this type are 'owned' by the current user.
947
-     * This is an empty string when the foreign key is on this model and when it isn't,
948
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
949
-     * (or transiently-related model)
950
-     *
951
-     * @return string
952
-     */
953
-    public function model_chain_to_wp_user()
954
-    {
955
-        return $this->_model_chain_to_wp_user;
956
-    }
957
-
958
-
959
-    /**
960
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
961
-     * like how registrations don't have a foreign key to wp_users, but the
962
-     * events they are for are), or is unrelated to wp users.
963
-     * generally available
964
-     *
965
-     * @return boolean
966
-     */
967
-    public function is_owned()
968
-    {
969
-        if ($this->model_chain_to_wp_user()) {
970
-            return true;
971
-        }
972
-        try {
973
-            $this->get_foreign_key_to('WP_User');
974
-            return true;
975
-        } catch (EE_Error $e) {
976
-            return false;
977
-        }
978
-    }
979
-
980
-
981
-    /**
982
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
983
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
984
-     * the model)
985
-     *
986
-     * @param array  $query_params
987
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
988
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
989
-     *                                  fields on the model, and the models we joined to in the query. However, you can
990
-     *                                  override this and set the select to "*", or a specific column name, like
991
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
992
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
993
-     *                                  the aliases used to refer to this selection, and values are to be
994
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
995
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
996
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
997
-     * @throws EE_Error
998
-     * @throws InvalidArgumentException
999
-     * @throws ReflectionException
1000
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1001
-     */
1002
-    protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1003
-    {
1004
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1005
-        $model_query_info         = $this->_create_model_query_info_carrier($query_params);
1006
-        $select_expressions       = $columns_to_select === null
1007
-            ? $this->_construct_default_select_sql($model_query_info)
1008
-            : '';
1009
-        if ($this->_custom_selections instanceof CustomSelects) {
1010
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1011
-            $select_expressions .= $select_expressions
1012
-                ? ', ' . $custom_expressions
1013
-                : $custom_expressions;
1014
-        }
1015
-
1016
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1017
-        return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1018
-    }
1019
-
1020
-
1021
-    /**
1022
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1023
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1024
-     * method of including extra select information.
1025
-     *
1026
-     * @param array             $query_params
1027
-     * @param null|array|string $columns_to_select
1028
-     * @return null|CustomSelects
1029
-     * @throws InvalidArgumentException
1030
-     */
1031
-    protected function getCustomSelection(array $query_params, $columns_to_select = null)
1032
-    {
1033
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1034
-            return null;
1035
-        }
1036
-        $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1037
-        $selects = is_string($selects) ? explode(',', $selects) : $selects;
1038
-        return new CustomSelects($selects);
1039
-    }
1040
-
1041
-
1042
-    /**
1043
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1044
-     * but you can use the model query params to more easily
1045
-     * take care of joins, field preparation etc.
1046
-     *
1047
-     * @param array  $query_params
1048
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1049
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1050
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1051
-     *                                  override this and set the select to "*", or a specific column name, like
1052
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1053
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1054
-     *                                  the aliases used to refer to this selection, and values are to be
1055
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1056
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1057
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1058
-     * @throws EE_Error
1059
-     * @throws ReflectionException
1060
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1061
-     */
1062
-    public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1063
-    {
1064
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1065
-    }
1066
-
1067
-
1068
-    /**
1069
-     * For creating a custom select statement
1070
-     *
1071
-     * @param array|string $columns_to_select either a string to be inserted directly as the select statement,
1072
-     *                                        or an array where keys are aliases, and values are arrays where 0=>the
1073
-     *                                        selection SQL, and 1=>is the datatype
1074
-     * @return string
1075
-     * @throws EE_Error
1076
-     */
1077
-    private function _construct_select_from_input($columns_to_select)
1078
-    {
1079
-        if (is_array($columns_to_select)) {
1080
-            $select_sql_array = [];
1081
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1082
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1083
-                    throw new EE_Error(
1084
-                        sprintf(
1085
-                            esc_html__(
1086
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1087
-                                'event_espresso'
1088
-                            ),
1089
-                            $selection_and_datatype,
1090
-                            $alias
1091
-                        )
1092
-                    );
1093
-                }
1094
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1095
-                    throw new EE_Error(
1096
-                        sprintf(
1097
-                            esc_html__(
1098
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1099
-                                'event_espresso'
1100
-                            ),
1101
-                            $selection_and_datatype[1],
1102
-                            $selection_and_datatype[0],
1103
-                            $alias,
1104
-                            implode(', ', $this->_valid_wpdb_data_types)
1105
-                        )
1106
-                    );
1107
-                }
1108
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1109
-            }
1110
-            $columns_to_select_string = implode(', ', $select_sql_array);
1111
-        } else {
1112
-            $columns_to_select_string = $columns_to_select;
1113
-        }
1114
-        return $columns_to_select_string;
1115
-    }
1116
-
1117
-
1118
-    /**
1119
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1120
-     *
1121
-     * @return string
1122
-     * @throws EE_Error
1123
-     */
1124
-    public function primary_key_name()
1125
-    {
1126
-        return $this->get_primary_key_field()->get_name();
1127
-    }
1128
-
1129
-
1130
-    /**
1131
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1132
-     * If there is no primary key on this model, $id is treated as primary key string
1133
-     *
1134
-     * @param mixed $id int or string, depending on the type of the model's primary key
1135
-     * @return EE_Base_Class
1136
-     * @throws EE_Error
1137
-     * @throws ReflectionException
1138
-     */
1139
-    public function get_one_by_ID($id)
1140
-    {
1141
-        if ($this->get_from_entity_map($id)) {
1142
-            return $this->get_from_entity_map($id);
1143
-        }
1144
-        return $this->get_one(
1145
-            $this->alter_query_params_to_restrict_by_ID(
1146
-                $id,
1147
-                ['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1148
-            )
1149
-        );
1150
-    }
1151
-
1152
-
1153
-    /**
1154
-     * Alters query parameters to only get items with this ID are returned.
1155
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1156
-     * or could just be a simple primary key ID
1157
-     *
1158
-     * @param int   $id
1159
-     * @param array $query_params see github link below for more info
1160
-     * @return array of normal query params,
1161
-     * @throws EE_Error
1162
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1163
-     */
1164
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1165
-    {
1166
-        if (! isset($query_params[0])) {
1167
-            $query_params[0] = [];
1168
-        }
1169
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1170
-        if ($conditions_from_id === null) {
1171
-            $query_params[0][ $this->primary_key_name() ] = $id;
1172
-        } else {
1173
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1174
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1175
-        }
1176
-        return $query_params;
1177
-    }
1178
-
1179
-
1180
-    /**
1181
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1182
-     * array. If no item is found, null is returned.
1183
-     *
1184
-     * @param array $query_params like EEM_Base's $query_params variable.
1185
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1186
-     * @throws EE_Error
1187
-     * @throws ReflectionException
1188
-     */
1189
-    public function get_one($query_params = [])
1190
-    {
1191
-        if (! is_array($query_params)) {
1192
-            EE_Error::doing_it_wrong(
1193
-                'EEM_Base::get_one',
1194
-                sprintf(
1195
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1196
-                    gettype($query_params)
1197
-                ),
1198
-                '4.6.0'
1199
-            );
1200
-            $query_params = [];
1201
-        }
1202
-        $query_params['limit'] = 1;
1203
-        $items                 = $this->get_all($query_params);
1204
-        if (empty($items)) {
1205
-            return null;
1206
-        }
1207
-        return array_shift($items);
1208
-    }
1209
-
1210
-
1211
-    /**
1212
-     * Returns the next x number of items in sequence from the given value as
1213
-     * found in the database matching the given query conditions.
1214
-     *
1215
-     * @param mixed $current_field_value    Value used for the reference point.
1216
-     * @param null  $field_to_order_by      What field is used for the
1217
-     *                                      reference point.
1218
-     * @param int   $limit                  How many to return.
1219
-     * @param array $query_params           Extra conditions on the query.
1220
-     * @param null  $columns_to_select      If left null, then an array of
1221
-     *                                      EE_Base_Class objects is returned,
1222
-     *                                      otherwise you can indicate just the
1223
-     *                                      columns you want returned.
1224
-     * @return EE_Base_Class[]|array
1225
-     * @throws EE_Error
1226
-     * @throws ReflectionException
1227
-     */
1228
-    public function next_x(
1229
-        $current_field_value,
1230
-        $field_to_order_by = null,
1231
-        $limit = 1,
1232
-        $query_params = [],
1233
-        $columns_to_select = null
1234
-    ) {
1235
-        return $this->_get_consecutive(
1236
-            $current_field_value,
1237
-            '>',
1238
-            $field_to_order_by,
1239
-            $limit,
1240
-            $query_params,
1241
-            $columns_to_select
1242
-        );
1243
-    }
1244
-
1245
-
1246
-    /**
1247
-     * Returns the previous x number of items in sequence from the given value
1248
-     * as found in the database matching the given query conditions.
1249
-     *
1250
-     * @param mixed $current_field_value    Value used for the reference point.
1251
-     * @param null  $field_to_order_by      What field is used for the
1252
-     *                                      reference point.
1253
-     * @param int   $limit                  How many to return.
1254
-     * @param array $query_params           Extra conditions on the query.
1255
-     * @param null  $columns_to_select      If left null, then an array of
1256
-     *                                      EE_Base_Class objects is returned,
1257
-     *                                      otherwise you can indicate just the
1258
-     *                                      columns you want returned.
1259
-     * @return EE_Base_Class[]|array
1260
-     * @throws EE_Error
1261
-     * @throws ReflectionException
1262
-     */
1263
-    public function previous_x(
1264
-        $current_field_value,
1265
-        $field_to_order_by = null,
1266
-        $limit = 1,
1267
-        $query_params = [],
1268
-        $columns_to_select = null
1269
-    ) {
1270
-        return $this->_get_consecutive(
1271
-            $current_field_value,
1272
-            '<',
1273
-            $field_to_order_by,
1274
-            $limit,
1275
-            $query_params,
1276
-            $columns_to_select
1277
-        );
1278
-    }
1279
-
1280
-
1281
-    /**
1282
-     * Returns the next item in sequence from the given value as found in the
1283
-     * database matching the given query conditions.
1284
-     *
1285
-     * @param mixed $current_field_value    Value used for the reference point.
1286
-     * @param null  $field_to_order_by      What field is used for the
1287
-     *                                      reference point.
1288
-     * @param array $query_params           Extra conditions on the query.
1289
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1290
-     *                                      object is returned, otherwise you
1291
-     *                                      can indicate just the columns you
1292
-     *                                      want and a single array indexed by
1293
-     *                                      the columns will be returned.
1294
-     * @return EE_Base_Class|null|array()
1295
-     * @throws EE_Error
1296
-     * @throws ReflectionException
1297
-     */
1298
-    public function next(
1299
-        $current_field_value,
1300
-        $field_to_order_by = null,
1301
-        $query_params = [],
1302
-        $columns_to_select = null
1303
-    ) {
1304
-        $results = $this->_get_consecutive(
1305
-            $current_field_value,
1306
-            '>',
1307
-            $field_to_order_by,
1308
-            1,
1309
-            $query_params,
1310
-            $columns_to_select
1311
-        );
1312
-        return empty($results) ? null : reset($results);
1313
-    }
1314
-
1315
-
1316
-    /**
1317
-     * Returns the previous item in sequence from the given value as found in
1318
-     * the database matching the given query conditions.
1319
-     *
1320
-     * @param mixed $current_field_value    Value used for the reference point.
1321
-     * @param null  $field_to_order_by      What field is used for the
1322
-     *                                      reference point.
1323
-     * @param array $query_params           Extra conditions on the query.
1324
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1325
-     *                                      object is returned, otherwise you
1326
-     *                                      can indicate just the columns you
1327
-     *                                      want and a single array indexed by
1328
-     *                                      the columns will be returned.
1329
-     * @return EE_Base_Class|null|array()
1330
-     * @throws EE_Error
1331
-     * @throws ReflectionException
1332
-     */
1333
-    public function previous(
1334
-        $current_field_value,
1335
-        $field_to_order_by = null,
1336
-        $query_params = [],
1337
-        $columns_to_select = null
1338
-    ) {
1339
-        $results = $this->_get_consecutive(
1340
-            $current_field_value,
1341
-            '<',
1342
-            $field_to_order_by,
1343
-            1,
1344
-            $query_params,
1345
-            $columns_to_select
1346
-        );
1347
-        return empty($results) ? null : reset($results);
1348
-    }
1349
-
1350
-
1351
-    /**
1352
-     * Returns the a consecutive number of items in sequence from the given
1353
-     * value as found in the database matching the given query conditions.
1354
-     *
1355
-     * @param mixed  $current_field_value   Value used for the reference point.
1356
-     * @param string $operand               What operand is used for the sequence.
1357
-     * @param string $field_to_order_by     What field is used for the reference point.
1358
-     * @param int    $limit                 How many to return.
1359
-     * @param array  $query_params          Extra conditions on the query.
1360
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1361
-     *                                      otherwise you can indicate just the columns you want returned.
1362
-     * @return EE_Base_Class[]|array
1363
-     * @throws EE_Error
1364
-     * @throws ReflectionException
1365
-     */
1366
-    protected function _get_consecutive(
1367
-        $current_field_value,
1368
-        $operand = '>',
1369
-        $field_to_order_by = null,
1370
-        $limit = 1,
1371
-        $query_params = [],
1372
-        $columns_to_select = null
1373
-    ) {
1374
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1375
-        if (empty($field_to_order_by)) {
1376
-            if ($this->has_primary_key_field()) {
1377
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1378
-            } else {
1379
-                if (WP_DEBUG) {
1380
-                    throw new EE_Error(
1381
-                        esc_html__(
1382
-                            'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1383
-                            'event_espresso'
1384
-                        )
1385
-                    );
1386
-                }
1387
-                EE_Error::add_error(__('There was an error with the query.', 'event_espresso'));
1388
-                return [];
1389
-            }
1390
-        }
1391
-        if (! is_array($query_params)) {
1392
-            EE_Error::doing_it_wrong(
1393
-                'EEM_Base::_get_consecutive',
1394
-                sprintf(
1395
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1396
-                    gettype($query_params)
1397
-                ),
1398
-                '4.6.0'
1399
-            );
1400
-            $query_params = [];
1401
-        }
1402
-        // let's add the where query param for consecutive look up.
1403
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1404
-        $query_params['limit']                 = $limit;
1405
-        // set direction
1406
-        $incoming_orderby         = isset($query_params['order_by']) ? (array)$query_params['order_by'] : [];
1407
-        $query_params['order_by'] = $operand === '>'
1408
-            ? [$field_to_order_by => 'ASC'] + $incoming_orderby
1409
-            : [$field_to_order_by => 'DESC'] + $incoming_orderby;
1410
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1411
-        if (empty($columns_to_select)) {
1412
-            return $this->get_all($query_params);
1413
-        }
1414
-        // getting just the fields
1415
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1416
-    }
1417
-
1418
-
1419
-    /**
1420
-     * This sets the _timezone property after model object has been instantiated.
1421
-     *
1422
-     * @param string|null $timezone valid PHP DateTimeZone timezone string
1423
-     */
1424
-    public function set_timezone($timezone)
1425
-    {
1426
-        // don't set the timezone if the incoming value is the same
1427
-        if (! empty($timezone) && $timezone === $this->_timezone) {
1428
-            return;
1429
-        }
1430
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
1431
-        // note we need to loop through relations and set the timezone on those objects as well.
1432
-        foreach ($this->_model_relations as $relation) {
1433
-            $relation->set_timezone($this->_timezone);
1434
-        }
1435
-        // and finally we do the same for any datetime fields
1436
-        foreach ($this->_fields as $field) {
1437
-            if ($field instanceof EE_Datetime_Field) {
1438
-                $field->set_timezone($this->_timezone);
1439
-            }
1440
-        }
1441
-    }
1442
-
1443
-
1444
-    /**
1445
-     * This just returns whatever is set for the current timezone.
1446
-     *
1447
-     * @access public
1448
-     * @return string
1449
-     */
1450
-    public function get_timezone()
1451
-    {
1452
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1453
-        if (empty($this->_timezone)) {
1454
-            foreach ($this->_fields as $field) {
1455
-                if ($field instanceof EE_Datetime_Field) {
1456
-                    $this->set_timezone($field->get_timezone());
1457
-                    break;
1458
-                }
1459
-            }
1460
-        }
1461
-        // if timezone STILL empty then return the default timezone for the site.
1462
-        if (empty($this->_timezone)) {
1463
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1464
-        }
1465
-        return $this->_timezone;
1466
-    }
1467
-
1468
-
1469
-    /**
1470
-     * This returns the date formats set for the given field name and also ensures that
1471
-     * $this->_timezone property is set correctly.
1472
-     *
1473
-     * @param string $field_name The name of the field the formats are being retrieved for.
1474
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1475
-     * @return array formats in an array with the date format first, and the time format last.
1476
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1477
-     * @since 4.6.x
1478
-     */
1479
-    public function get_formats_for($field_name, $pretty = false)
1480
-    {
1481
-        $field_settings = $this->field_settings_for($field_name);
1482
-        // if not a valid EE_Datetime_Field then throw error
1483
-        if (! $field_settings instanceof EE_Datetime_Field) {
1484
-            throw new EE_Error(
1485
-                sprintf(
1486
-                    esc_html__(
1487
-                        'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1488
-                        'event_espresso'
1489
-                    ),
1490
-                    $field_name
1491
-                )
1492
-            );
1493
-        }
1494
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1495
-        // the field.
1496
-        $this->_timezone = $field_settings->get_timezone();
1497
-        return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1498
-    }
1499
-
1500
-
1501
-    /**
1502
-     * This returns the current time in a format setup for a query on this model.
1503
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1504
-     * it will return:
1505
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1506
-     *  NOW
1507
-     *  - or a unix timestamp (equivalent to time())
1508
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1509
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1510
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1511
-     *
1512
-     * @param string $field_name       The field the current time is needed for.
1513
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1514
-     *                                 formatted string matching the set format for the field in the set timezone will
1515
-     *                                 be returned.
1516
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1517
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1518
-     *                                 exception is triggered.
1519
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1520
-     * @throws Exception
1521
-     * @since 4.6.x
1522
-     */
1523
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1524
-    {
1525
-        $formats  = $this->get_formats_for($field_name);
1526
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1527
-        if ($timestamp) {
1528
-            return $DateTime->format('U');
1529
-        }
1530
-        // not returning timestamp, so return formatted string in timezone.
1531
-        switch ($what) {
1532
-            case 'time':
1533
-                return $DateTime->format($formats[1]);
1534
-            case 'date':
1535
-                return $DateTime->format($formats[0]);
1536
-            default:
1537
-                return $DateTime->format(implode(' ', $formats));
1538
-        }
1539
-    }
1540
-
1541
-
1542
-    /**
1543
-     * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1544
-     * for the model are.  Returns a DateTime object.
1545
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1546
-     * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1547
-     * ignored.
1548
-     *
1549
-     * @param string $field_name      The field being setup.
1550
-     * @param string $timestring      The date time string being used.
1551
-     * @param string $incoming_format The format for the time string.
1552
-     * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1553
-     *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1554
-     *                                format is
1555
-     *                                'U', this is ignored.
1556
-     * @return DateTime
1557
-     * @throws EE_Error
1558
-     */
1559
-    public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1560
-    {
1561
-        // just using this to ensure the timezone is set correctly internally
1562
-        $this->get_formats_for($field_name);
1563
-        // load EEH_DTT_Helper
1564
-        $set_timezone     = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1565
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1566
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1567
-        return DbSafeDateTime::createFromDateTime($incomingDateTime);
1568
-    }
1569
-
1570
-
1571
-    /**
1572
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1573
-     *
1574
-     * @return EE_Table_Base[]
1575
-     */
1576
-    public function get_tables()
1577
-    {
1578
-        return $this->_tables;
1579
-    }
1580
-
1581
-
1582
-    /**
1583
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1584
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1585
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1586
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1587
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1588
-     * model object with EVT_ID = 1
1589
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1590
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1591
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1592
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1593
-     * are not specified)
1594
-     *
1595
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1596
-     *                                         columns!), values are strings, integers, floats, and maybe arrays if
1597
-     *                                         they
1598
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1599
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1600
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1601
-     *                                         ID=34, we'd use this method as follows:
1602
-     *                                         EEM_Transaction::instance()->update(
1603
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1604
-     *                                         array(array('TXN_ID'=>34)));
1605
-     * @param array   $query_params            Eg, consider updating Question's QST_admin_label field is of type
1606
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1607
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1608
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1609
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1610
-     *                                         TRUE, it is assumed that you've already called
1611
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1612
-     *                                         malicious javascript. However, if
1613
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1614
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1615
-     *                                         and every other field, before insertion. We provide this parameter
1616
-     *                                         because model objects perform their prepare_for_set function on all
1617
-     *                                         their values, and so don't need to be called again (and in many cases,
1618
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1619
-     *                                         prepare_for_set method...)
1620
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1621
-     *                                         in this model's entity map according to $fields_n_values that match
1622
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1623
-     *                                         by setting this to FALSE, but be aware that model objects being used
1624
-     *                                         could get out-of-sync with the database
1625
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1626
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1627
-     *                                         bad)
1628
-     * @throws EE_Error
1629
-     * @throws ReflectionException
1630
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1631
-     */
1632
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1633
-    {
1634
-        if (! is_array($query_params)) {
1635
-            EE_Error::doing_it_wrong(
1636
-                'EEM_Base::update',
1637
-                sprintf(
1638
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1639
-                    gettype($query_params)
1640
-                ),
1641
-                '4.6.0'
1642
-            );
1643
-            $query_params = [];
1644
-        }
1645
-        /**
1646
-         * Action called before a model update call has been made.
1647
-         *
1648
-         * @param EEM_Base $model
1649
-         * @param array    $fields_n_values the updated fields and their new values
1650
-         * @param array    $query_params
1651
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1652
-         */
1653
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1654
-        /**
1655
-         * Filters the fields about to be updated given the query parameters. You can provide the
1656
-         * $query_params to $this->get_all() to find exactly which records will be updated
1657
-         *
1658
-         * @param array    $fields_n_values fields and their new values
1659
-         * @param EEM_Base $model           the model being queried
1660
-         * @param array    $query_params
1661
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1662
-         */
1663
-        $fields_n_values = (array)apply_filters(
1664
-            'FHEE__EEM_Base__update__fields_n_values',
1665
-            $fields_n_values,
1666
-            $this,
1667
-            $query_params
1668
-        );
1669
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1670
-        // to do that, for each table, verify that it's PK isn't null.
1671
-        $tables = $this->get_tables();
1672
-        // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1673
-        // NOTE: we should make this code more efficient by NOT querying twice
1674
-        // before the real update, but that needs to first go through ALPHA testing
1675
-        // as it's dangerous. says Mike August 8 2014
1676
-        // we want to make sure the default_where strategy is ignored
1677
-        $this->_ignore_where_strategy = true;
1678
-        $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1679
-        foreach ($wpdb_select_results as $wpdb_result) {
1680
-            // type cast stdClass as array
1681
-            $wpdb_result = (array)$wpdb_result;
1682
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1683
-            if ($this->has_primary_key_field()) {
1684
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1685
-            } else {
1686
-                // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1687
-                $main_table_pk_value = null;
1688
-            }
1689
-            // if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1690
-            // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1691
-            if (count($tables) > 1) {
1692
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1693
-                // in that table, and so we'll want to insert one
1694
-                foreach ($tables as $table_obj) {
1695
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1696
-                    // if there is no private key for this table on the results, it means there's no entry
1697
-                    // in this table, right? so insert a row in the current table, using any fields available
1698
-                    if (
1699
-                    ! (array_key_exists($this_table_pk_column, $wpdb_result)
1700
-                       && $wpdb_result[ $this_table_pk_column ])
1701
-                    ) {
1702
-                        $success = $this->_insert_into_specific_table(
1703
-                            $table_obj,
1704
-                            $fields_n_values,
1705
-                            $main_table_pk_value
1706
-                        );
1707
-                        // if we died here, report the error
1708
-                        if (! $success) {
1709
-                            return false;
1710
-                        }
1711
-                    }
1712
-                }
1713
-            }
1714
-            //              //and now check that if we have cached any models by that ID on the model, that
1715
-            //              //they also get updated properly
1716
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1717
-            //              if( $model_object ){
1718
-            //                  foreach( $fields_n_values as $field => $value ){
1719
-            //                      $model_object->set($field, $value);
1720
-            // let's make sure default_where strategy is followed now
1721
-            $this->_ignore_where_strategy = false;
1722
-        }
1723
-        // if we want to keep model objects in sync, AND
1724
-        // if this wasn't called from a model object (to update itself)
1725
-        // then we want to make sure we keep all the existing
1726
-        // model objects in sync with the db
1727
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1728
-            if ($this->has_primary_key_field()) {
1729
-                $model_objs_affected_ids = $this->get_col($query_params);
1730
-            } else {
1731
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1732
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params);
1733
-                $model_objs_affected_ids     = [];
1734
-                foreach ($models_affected_key_columns as $row) {
1735
-                    $combined_index_key                             = $this->get_index_primary_key_string($row);
1736
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1737
-                }
1738
-            }
1739
-            if (! $model_objs_affected_ids) {
1740
-                // wait wait wait- if nothing was affected let's stop here
1741
-                return 0;
1742
-            }
1743
-            foreach ($model_objs_affected_ids as $id) {
1744
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1745
-                if ($model_obj_in_entity_map) {
1746
-                    foreach ($fields_n_values as $field => $new_value) {
1747
-                        $model_obj_in_entity_map->set($field, $new_value);
1748
-                    }
1749
-                }
1750
-            }
1751
-            // if there is a primary key on this model, we can now do a slight optimization
1752
-            if ($this->has_primary_key_field()) {
1753
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1754
-                $query_params = [
1755
-                    [$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1756
-                    'limit'                    => count($model_objs_affected_ids),
1757
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1758
-                ];
1759
-            }
1760
-        }
1761
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1762
-        $SQL              = "UPDATE "
1763
-                            . $model_query_info->get_full_join_sql()
1764
-                            . " SET "
1765
-                            . $this->_construct_update_sql($fields_n_values)
1766
-                            . $model_query_info->get_where_sql(
1767
-            );// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1768
-        $rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1769
-        /**
1770
-         * Action called after a model update call has been made.
1771
-         *
1772
-         * @param EEM_Base $model
1773
-         * @param array    $fields_n_values the updated fields and their new values
1774
-         * @param array    $query_params
1775
-         * @param int      $rows_affected
1776
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1777
-         */
1778
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1779
-        return $rows_affected;// how many supposedly got updated
1780
-    }
1781
-
1782
-
1783
-    /**
1784
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1785
-     * are teh values of the field specified (or by default the primary key field)
1786
-     * that matched the query params. Note that you should pass the name of the
1787
-     * model FIELD, not the database table's column name.
1788
-     *
1789
-     * @param array  $query_params
1790
-     * @param string $field_to_select
1791
-     * @return array just like $wpdb->get_col()
1792
-     * @throws EE_Error
1793
-     * @throws ReflectionException
1794
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1795
-     */
1796
-    public function get_col($query_params = [], $field_to_select = null)
1797
-    {
1798
-        if ($field_to_select) {
1799
-            $field = $this->field_settings_for($field_to_select);
1800
-        } elseif ($this->has_primary_key_field()) {
1801
-            $field = $this->get_primary_key_field();
1802
-        } else {
1803
-            $field_settings = $this->field_settings();
1804
-            // no primary key, just grab the first column
1805
-            $field = reset($field_settings);
1806
-        }
1807
-        $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1808
-        $select_expressions = $field->get_qualified_column();
1809
-        $SQL                =
1810
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1811
-        return $this->_do_wpdb_query('get_col', [$SQL]);
1812
-    }
1813
-
1814
-
1815
-    /**
1816
-     * Returns a single column value for a single row from the database
1817
-     *
1818
-     * @param array  $query_params
1819
-     * @param string $field_to_select
1820
-     * @return string
1821
-     * @throws EE_Error
1822
-     * @throws ReflectionException
1823
-     * @see EEM_Base::get_col()
1824
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1825
-     */
1826
-    public function get_var($query_params = [], $field_to_select = null)
1827
-    {
1828
-        $query_params['limit'] = 1;
1829
-        $col                   = $this->get_col($query_params, $field_to_select);
1830
-        if (! empty($col)) {
1831
-            return reset($col);
1832
-        }
1833
-        return null;
1834
-    }
1835
-
1836
-
1837
-    /**
1838
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1839
-     * time?', Question.desc='what do you think?'..." Values are filtered through wpdb->prepare to avoid against SQL
1840
-     * injection, but currently no further filtering is done
1841
-     *
1842
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1843
-     *                               be updated to in the DB
1844
-     * @return string of SQL
1845
-     * @throws EE_Error
1846
-     * @global      $wpdb
1847
-     */
1848
-    public function _construct_update_sql($fields_n_values)
1849
-    {
1850
-        global $wpdb;
1851
-        $cols_n_values = [];
1852
-        foreach ($fields_n_values as $field_name => $value) {
1853
-            $field_obj = $this->field_settings_for($field_name);
1854
-            // if the value is NULL, we want to assign the value to that.
1855
-            // wpdb->prepare doesn't really handle that properly
1856
-            $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1857
-            $value_sql       = $prepared_value === null ? 'NULL'
1858
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1859
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1860
-        }
1861
-        return implode(",", $cols_n_values);
1862
-    }
1863
-
1864
-
1865
-    /**
1866
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1867
-     * Performs a HARD delete, meaning the database row should always be removed,
1868
-     * not just have a flag field on it switched
1869
-     * Wrapper for EEM_Base::delete_permanently()
1870
-     *
1871
-     * @param mixed   $id
1872
-     * @param boolean $allow_blocking
1873
-     * @return int the number of rows deleted
1874
-     * @throws EE_Error
1875
-     * @throws ReflectionException
1876
-     */
1877
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1878
-    {
1879
-        return $this->delete_permanently(
1880
-            [
1881
-                [$this->get_primary_key_field()->get_name() => $id],
1882
-                'limit' => 1,
1883
-            ],
1884
-            $allow_blocking
1885
-        );
1886
-    }
1887
-
1888
-
1889
-    /**
1890
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1891
-     * Wrapper for EEM_Base::delete()
1892
-     *
1893
-     * @param mixed   $id
1894
-     * @param boolean $allow_blocking
1895
-     * @return int the number of rows deleted
1896
-     * @throws EE_Error
1897
-     * @throws ReflectionException
1898
-     */
1899
-    public function delete_by_ID($id, $allow_blocking = true)
1900
-    {
1901
-        return $this->delete(
1902
-            [
1903
-                [$this->get_primary_key_field()->get_name() => $id],
1904
-                'limit' => 1,
1905
-            ],
1906
-            $allow_blocking
1907
-        );
1908
-    }
1909
-
1910
-
1911
-    /**
1912
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1913
-     * meaning if the model has a field that indicates its been "trashed" or
1914
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1915
-     *
1916
-     * @param array   $query_params
1917
-     * @param boolean $allow_blocking
1918
-     * @return int how many rows got deleted
1919
-     * @throws EE_Error
1920
-     * @throws ReflectionException
1921
-     * @see EEM_Base::delete_permanently
1922
-     */
1923
-    public function delete($query_params, $allow_blocking = true)
1924
-    {
1925
-        return $this->delete_permanently($query_params, $allow_blocking);
1926
-    }
1927
-
1928
-
1929
-    /**
1930
-     * Deletes the model objects that meet the query params. Note: this method is overridden
1931
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1932
-     * as archived, not actually deleted
1933
-     *
1934
-     * @param array   $query_params
1935
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1936
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1937
-     *                                deletes regardless of other objects which may depend on it. Its generally
1938
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1939
-     *                                DB
1940
-     * @return int how many rows got deleted
1941
-     * @throws EE_Error
1942
-     * @throws ReflectionException
1943
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1944
-     */
1945
-    public function delete_permanently($query_params, $allow_blocking = true)
1946
-    {
1947
-        /**
1948
-         * Action called just before performing a real deletion query. You can use the
1949
-         * model and its $query_params to find exactly which items will be deleted
1950
-         *
1951
-         * @param EEM_Base $model
1952
-         * @param array    $query_params
1953
-         * @param boolean  $allow_blocking whether or not to allow related model objects
1954
-         *                                 to block (prevent) this deletion
1955
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1956
-         */
1957
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1958
-        // some MySQL databases may be running safe mode, which may restrict
1959
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
1960
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
1961
-        // to delete them
1962
-        $items_for_deletion           = $this->_get_all_wpdb_results($query_params);
1963
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1964
-        $deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
1965
-            $columns_and_ids_for_deleting
1966
-        );
1967
-        /**
1968
-         * Allows client code to act on the items being deleted before the query is actually executed.
1969
-         *
1970
-         * @param EEM_Base $this                            The model instance being acted on.
1971
-         * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
1972
-         * @param bool     $allow_blocking                  @see param description in method phpdoc block.
1973
-         * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
1974
-         *                                                  derived from the incoming query parameters.
1975
-         * @see details on the structure of this array in the phpdocs
1976
-         *                                                  for the `_get_ids_for_delete_method`
1977
-         *
1978
-         */
1979
-        do_action(
1980
-            'AHEE__EEM_Base__delete__before_query',
1981
-            $this,
1982
-            $query_params,
1983
-            $allow_blocking,
1984
-            $columns_and_ids_for_deleting
1985
-        );
1986
-        if ($deletion_where_query_part) {
1987
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
1988
-            $table_aliases    = array_keys($this->_tables);
1989
-            $SQL              = "DELETE "
1990
-                                . implode(", ", $table_aliases)
1991
-                                . " FROM "
1992
-                                . $model_query_info->get_full_join_sql()
1993
-                                . " WHERE "
1994
-                                . $deletion_where_query_part;
1995
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
1996
-        } else {
1997
-            $rows_deleted = 0;
1998
-        }
1999
-
2000
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2001
-        // there was no error with the delete query.
2002
-        if (
2003
-            $this->has_primary_key_field()
2004
-            && $rows_deleted !== false
2005
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2006
-        ) {
2007
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2008
-            foreach ($ids_for_removal as $id) {
2009
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2010
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2011
-                }
2012
-            }
2013
-
2014
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2015
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2016
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2017
-            // (although it is possible).
2018
-            // Note this can be skipped by using the provided filter and returning false.
2019
-            if (
2020
-            apply_filters(
2021
-                'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2022
-                ! $this instanceof EEM_Extra_Meta,
2023
-                $this
2024
-            )
2025
-            ) {
2026
-                EEM_Extra_Meta::instance()->delete_permanently(
2027
-                    [
2028
-                        0 => [
2029
-                            'EXM_type' => $this->get_this_model_name(),
2030
-                            'OBJ_ID'   => [
2031
-                                'IN',
2032
-                                $ids_for_removal,
2033
-                            ],
2034
-                        ],
2035
-                    ]
2036
-                );
2037
-            }
2038
-        }
2039
-
2040
-        /**
2041
-         * Action called just after performing a real deletion query. Although at this point the
2042
-         * items should have been deleted
2043
-         *
2044
-         * @param EEM_Base $model
2045
-         * @param array    $query_params
2046
-         * @param int      $rows_deleted
2047
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2048
-         */
2049
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2050
-        return $rows_deleted;// how many supposedly got deleted
2051
-    }
2052
-
2053
-
2054
-    /**
2055
-     * Checks all the relations that throw error messages when there are blocking related objects
2056
-     * for related model objects. If there are any related model objects on those relations,
2057
-     * adds an EE_Error, and return true
2058
-     *
2059
-     * @param EE_Base_Class|int $this_model_obj_or_id
2060
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2061
-     *                                                 should be ignored when determining whether there are related
2062
-     *                                                 model objects which block this model object's deletion. Useful
2063
-     *                                                 if you know A is related to B and are considering deleting A,
2064
-     *                                                 but want to see if A has any other objects blocking its deletion
2065
-     *                                                 before removing the relation between A and B
2066
-     * @return boolean
2067
-     * @throws EE_Error
2068
-     * @throws ReflectionException
2069
-     */
2070
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2071
-    {
2072
-        // first, if $ignore_this_model_obj was supplied, get its model
2073
-        if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2074
-            $ignored_model = $ignore_this_model_obj->get_model();
2075
-        } else {
2076
-            $ignored_model = null;
2077
-        }
2078
-        // now check all the relations of $this_model_obj_or_id and see if there
2079
-        // are any related model objects blocking it?
2080
-        $is_blocked = false;
2081
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2082
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2083
-                // if $ignore_this_model_obj was supplied, then for the query
2084
-                // on that model needs to be told to ignore $ignore_this_model_obj
2085
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2086
-                    $related_model_objects = $relation_obj->get_all_related(
2087
-                        $this_model_obj_or_id,
2088
-                        [
2089
-                            [
2090
-                                $ignored_model->get_primary_key_field()->get_name() => [
2091
-                                    '!=',
2092
-                                    $ignore_this_model_obj->ID(),
2093
-                                ],
2094
-                            ],
2095
-                        ]
2096
-                    );
2097
-                } else {
2098
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2099
-                }
2100
-                if ($related_model_objects) {
2101
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2102
-                    $is_blocked = true;
2103
-                }
2104
-            }
2105
-        }
2106
-        return $is_blocked;
2107
-    }
2108
-
2109
-
2110
-    /**
2111
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2112
-     *
2113
-     * @param array $row_results_for_deleting
2114
-     * @param bool  $allow_blocking
2115
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2116
-     *                              model DOES have a primary_key_field, then the array will be a simple single
2117
-     *                              dimension array where the key is the fully qualified primary key column and the
2118
-     *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2119
-     *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2120
-     *                              be a two dimensional array where each element is a group of columns and values that
2121
-     *                              get deleted. Example: array(
2122
-     *                              0 => array(
2123
-     *                              'Term_Relationship.object_id' => 1
2124
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2125
-     *                              ),
2126
-     *                              1 => array(
2127
-     *                              'Term_Relationship.object_id' => 1
2128
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2129
-     *                              )
2130
-     *                              )
2131
-     * @throws EE_Error
2132
-     * @throws ReflectionException
2133
-     */
2134
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2135
-    {
2136
-        $ids_to_delete_indexed_by_column = [];
2137
-        if ($this->has_primary_key_field()) {
2138
-            $primary_table                   = $this->_get_main_table();
2139
-            $ids_to_delete_indexed_by_column = $query = [];
2140
-            foreach ($row_results_for_deleting as $item_to_delete) {
2141
-                // before we mark this item for deletion,
2142
-                // make sure there's no related entities blocking its deletion (if we're checking)
2143
-                if (
2144
-                    $allow_blocking
2145
-                    && $this->delete_is_blocked_by_related_models(
2146
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2147
-                    )
2148
-                ) {
2149
-                    continue;
2150
-                }
2151
-                // primary table deletes
2152
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2153
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2154
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2155
-                }
2156
-            }
2157
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2158
-            $fields = $this->get_combined_primary_key_fields();
2159
-            foreach ($row_results_for_deleting as $item_to_delete) {
2160
-                $ids_to_delete_indexed_by_column_for_row = [];
2161
-                foreach ($fields as $cpk_field) {
2162
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2163
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2164
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2165
-                    }
2166
-                }
2167
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2168
-            }
2169
-        } else {
2170
-            // so there's no primary key and no combined key...
2171
-            // sorry, can't help you
2172
-            throw new EE_Error(
2173
-                sprintf(
2174
-                    esc_html__(
2175
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2176
-                        "event_espresso"
2177
-                    ),
2178
-                    get_class($this)
2179
-                )
2180
-            );
2181
-        }
2182
-        return $ids_to_delete_indexed_by_column;
2183
-    }
2184
-
2185
-
2186
-    /**
2187
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2188
-     * the corresponding query_part for the query performing the delete.
2189
-     *
2190
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2191
-     * @return string
2192
-     * @throws EE_Error
2193
-     */
2194
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2195
-    {
2196
-        $query_part = '';
2197
-        if (empty($ids_to_delete_indexed_by_column)) {
2198
-            return $query_part;
2199
-        } elseif ($this->has_primary_key_field()) {
2200
-            $query = [];
2201
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2202
-                // make sure we have unique $ids
2203
-                $ids     = array_unique($ids);
2204
-                $query[] = $column . ' IN(' . implode(',', $ids) . ')';
2205
-            }
2206
-            $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2207
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2208
-            $ways_to_identify_a_row = [];
2209
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2210
-                $values_for_each_combined_primary_key_for_a_row = [];
2211
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2212
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2213
-                }
2214
-                $ways_to_identify_a_row[] = '('
2215
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2216
-                                            . ')';
2217
-            }
2218
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2219
-        }
2220
-        return $query_part;
2221
-    }
2222
-
2223
-
2224
-    /**
2225
-     * Gets the model field by the fully qualified name
2226
-     *
2227
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2228
-     * @return EE_Model_Field_Base
2229
-     * @throws EE_Error
2230
-     */
2231
-    public function get_field_by_column($qualified_column_name)
2232
-    {
2233
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2234
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2235
-                return $field_obj;
2236
-            }
2237
-        }
2238
-        throw new EE_Error(
2239
-            sprintf(
2240
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2241
-                $this->get_this_model_name(),
2242
-                $qualified_column_name
2243
-            )
2244
-        );
2245
-    }
2246
-
2247
-
2248
-    /**
2249
-     * Count all the rows that match criteria the model query params.
2250
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2251
-     * column
2252
-     *
2253
-     * @param array  $query_params
2254
-     * @param string $field_to_count field on model to count by (not column name)
2255
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2256
-     *                               that by the setting $distinct to TRUE;
2257
-     * @return int
2258
-     * @throws EE_Error
2259
-     * @throws ReflectionException
2260
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2261
-     */
2262
-    public function count($query_params = [], $field_to_count = null, $distinct = false)
2263
-    {
2264
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2265
-        if ($field_to_count) {
2266
-            $field_obj       = $this->field_settings_for($field_to_count);
2267
-            $column_to_count = $field_obj->get_qualified_column();
2268
-        } elseif ($this->has_primary_key_field()) {
2269
-            $pk_field_obj    = $this->get_primary_key_field();
2270
-            $column_to_count = $pk_field_obj->get_qualified_column();
2271
-        } else {
2272
-            // there's no primary key
2273
-            // if we're counting distinct items, and there's no primary key,
2274
-            // we need to list out the columns for distinction;
2275
-            // otherwise we can just use star
2276
-            if ($distinct) {
2277
-                $columns_to_use = [];
2278
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2279
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2280
-                }
2281
-                $column_to_count = implode(',', $columns_to_use);
2282
-            } else {
2283
-                $column_to_count = '*';
2284
-            }
2285
-        }
2286
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2287
-        $SQL             =
2288
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2289
-        return (int)$this->_do_wpdb_query('get_var', [$SQL]);
2290
-    }
2291
-
2292
-
2293
-    /**
2294
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2295
-     *
2296
-     * @param array  $query_params
2297
-     * @param string $field_to_sum name of field (array key in $_fields array)
2298
-     * @return float
2299
-     * @throws EE_Error
2300
-     * @throws ReflectionException
2301
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2302
-     */
2303
-    public function sum($query_params, $field_to_sum = null)
2304
-    {
2305
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2306
-        if ($field_to_sum) {
2307
-            $field_obj = $this->field_settings_for($field_to_sum);
2308
-        } else {
2309
-            $field_obj = $this->get_primary_key_field();
2310
-        }
2311
-        $column_to_count = $field_obj->get_qualified_column();
2312
-        $SQL             =
2313
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2314
-        $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2315
-        $data_type       = $field_obj->get_wpdb_data_type();
2316
-        if ($data_type === '%d' || $data_type === '%s') {
2317
-            return (float)$return_value;
2318
-        }
2319
-        // must be %f
2320
-        return (float)$return_value;
2321
-    }
2322
-
2323
-
2324
-    /**
2325
-     * Just calls the specified method on $wpdb with the given arguments
2326
-     * Consolidates a little extra error handling code
2327
-     *
2328
-     * @param string $wpdb_method
2329
-     * @param array  $arguments_to_provide
2330
-     * @return mixed
2331
-     * @throws EE_Error
2332
-     * @global wpdb  $wpdb
2333
-     */
2334
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2335
-    {
2336
-        // if we're in maintenance mode level 2, DON'T run any queries
2337
-        // because level 2 indicates the database needs updating and
2338
-        // is probably out of sync with the code
2339
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2340
-            throw new EE_Error(
2341
-                sprintf(
2342
-                    esc_html__(
2343
-                        "Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2344
-                        "event_espresso"
2345
-                    )
2346
-                )
2347
-            );
2348
-        }
2349
-        global $wpdb;
2350
-        if (! method_exists($wpdb, $wpdb_method)) {
2351
-            throw new EE_Error(
2352
-                sprintf(
2353
-                    esc_html__(
2354
-                        'There is no method named "%s" on Wordpress\' $wpdb object',
2355
-                        'event_espresso'
2356
-                    ),
2357
-                    $wpdb_method
2358
-                )
2359
-            );
2360
-        }
2361
-        $old_show_errors_value = $wpdb->show_errors;
2362
-        if (WP_DEBUG) {
2363
-            $wpdb->show_errors(false);
2364
-        }
2365
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2366
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2367
-        if (WP_DEBUG) {
2368
-            $wpdb->show_errors($old_show_errors_value);
2369
-            if (! empty($wpdb->last_error)) {
2370
-                throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2371
-            }
2372
-            if ($result === false) {
2373
-                throw new EE_Error(
2374
-                    sprintf(
2375
-                        esc_html__(
2376
-                            'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2377
-                            'event_espresso'
2378
-                        ),
2379
-                        $wpdb_method,
2380
-                        var_export($arguments_to_provide, true)
2381
-                    )
2382
-                );
2383
-            }
2384
-        } elseif ($result === false) {
2385
-            EE_Error::add_error(
2386
-                sprintf(
2387
-                    esc_html__(
2388
-                        'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2389
-                        'event_espresso'
2390
-                    ),
2391
-                    $wpdb_method,
2392
-                    var_export($arguments_to_provide, true),
2393
-                    $wpdb->last_error
2394
-                ),
2395
-                __FILE__,
2396
-                __FUNCTION__,
2397
-                __LINE__
2398
-            );
2399
-        }
2400
-        return $result;
2401
-    }
2402
-
2403
-
2404
-    /**
2405
-     * Attempts to run the indicated WPDB method with the provided arguments,
2406
-     * and if there's an error tries to verify the DB is correct. Uses
2407
-     * the static property EEM_Base::$_db_verification_level to determine whether
2408
-     * we should try to fix the EE core db, the addons, or just give up
2409
-     *
2410
-     * @param string $wpdb_method
2411
-     * @param array  $arguments_to_provide
2412
-     * @return mixed
2413
-     * @throws EE_Error
2414
-     */
2415
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2416
-    {
2417
-        global $wpdb;
2418
-        $wpdb->last_error = null;
2419
-        $result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2420
-        // was there an error running the query? but we don't care on new activations
2421
-        // (we're going to setup the DB anyway on new activations)
2422
-        if (
2423
-            ($result === false || ! empty($wpdb->last_error))
2424
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2425
-        ) {
2426
-            switch (EEM_Base::$_db_verification_level) {
2427
-                case EEM_Base::db_verified_none:
2428
-                    // let's double-check core's DB
2429
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2430
-                    break;
2431
-                case EEM_Base::db_verified_core:
2432
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2433
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2434
-                    break;
2435
-                case EEM_Base::db_verified_addons:
2436
-                    // ummmm... you in trouble
2437
-                    return $result;
2438
-            }
2439
-            if (! empty($error_message)) {
2440
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2441
-                trigger_error($error_message);
2442
-            }
2443
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2444
-        }
2445
-        return $result;
2446
-    }
2447
-
2448
-
2449
-    /**
2450
-     * Verifies the EE core database is up-to-date and records that we've done it on
2451
-     * EEM_Base::$_db_verification_level
2452
-     *
2453
-     * @param string $wpdb_method
2454
-     * @param array  $arguments_to_provide
2455
-     * @return string
2456
-     * @throws EE_Error
2457
-     */
2458
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2459
-    {
2460
-        global $wpdb;
2461
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2462
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2463
-        $error_message                    = sprintf(
2464
-            esc_html__(
2465
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2466
-                'event_espresso'
2467
-            ),
2468
-            $wpdb->last_error,
2469
-            $wpdb_method,
2470
-            wp_json_encode($arguments_to_provide)
2471
-        );
2472
-        EE_System::instance()->initialize_db_if_no_migrations_required();
2473
-        return $error_message;
2474
-    }
2475
-
2476
-
2477
-    /**
2478
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2479
-     * EEM_Base::$_db_verification_level
2480
-     *
2481
-     * @param $wpdb_method
2482
-     * @param $arguments_to_provide
2483
-     * @return string
2484
-     * @throws EE_Error
2485
-     */
2486
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2487
-    {
2488
-        global $wpdb;
2489
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2490
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2491
-        $error_message                    = sprintf(
2492
-            esc_html__(
2493
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2494
-                'event_espresso'
2495
-            ),
2496
-            $wpdb->last_error,
2497
-            $wpdb_method,
2498
-            wp_json_encode($arguments_to_provide)
2499
-        );
2500
-        EE_System::instance()->initialize_addons();
2501
-        return $error_message;
2502
-    }
2503
-
2504
-
2505
-    /**
2506
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2507
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2508
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2509
-     * ..."
2510
-     *
2511
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2512
-     * @return string
2513
-     */
2514
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2515
-    {
2516
-        return " FROM " . $model_query_info->get_full_join_sql() .
2517
-               $model_query_info->get_where_sql() .
2518
-               $model_query_info->get_group_by_sql() .
2519
-               $model_query_info->get_having_sql() .
2520
-               $model_query_info->get_order_by_sql() .
2521
-               $model_query_info->get_limit_sql();
2522
-    }
2523
-
2524
-
2525
-    /**
2526
-     * Set to easily debug the next X queries ran from this model.
2527
-     *
2528
-     * @param int $count
2529
-     */
2530
-    public function show_next_x_db_queries($count = 1)
2531
-    {
2532
-        $this->_show_next_x_db_queries = $count;
2533
-    }
2534
-
2535
-
2536
-    /**
2537
-     * @param $sql_query
2538
-     */
2539
-    public function show_db_query_if_previously_requested($sql_query)
2540
-    {
2541
-        if ($this->_show_next_x_db_queries > 0) {
2542
-            echo $sql_query;
2543
-            $this->_show_next_x_db_queries--;
2544
-        }
2545
-    }
2546
-
2547
-
2548
-    /**
2549
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2550
-     * There are the 3 cases:
2551
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2552
-     * $otherModelObject has no ID, it is first saved.
2553
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2554
-     * has no ID, it is first saved.
2555
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2556
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2557
-     * join table
2558
-     *
2559
-     * @param EE_Base_Class                     /int $thisModelObject
2560
-     * @param EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2561
-     * @param string $relationName                     , key in EEM_Base::_relations
2562
-     *                                                 an attendee to a group, you also want to specify which role they
2563
-     *                                                 will have in that group. So you would use this parameter to
2564
-     *                                                 specify array('role-column-name'=>'role-id')
2565
-     * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2566
-     *                                                 to for relation to methods that allow you to further specify
2567
-     *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2568
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2569
-     *                                                 because these will be inserted in any new rows created as well.
2570
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2571
-     * @throws EE_Error
2572
-     */
2573
-    public function add_relationship_to(
2574
-        $id_or_obj,
2575
-        $other_model_id_or_obj,
2576
-        $relationName,
2577
-        $extra_join_model_fields_n_values = []
2578
-    ) {
2579
-        $relation_obj = $this->related_settings_for($relationName);
2580
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2581
-    }
2582
-
2583
-
2584
-    /**
2585
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2586
-     * There are the 3 cases:
2587
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2588
-     * error
2589
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2590
-     * an error
2591
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2592
-     *
2593
-     * @param EE_Base_Class /int $id_or_obj
2594
-     * @param EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2595
-     * @param string $relationName key in EEM_Base::_relations
2596
-     * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2597
-     *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2598
-     *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2599
-     *                             because these will be inserted in any new rows created as well.
2600
-     * @return boolean of success
2601
-     * @throws EE_Error
2602
-     */
2603
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2604
-    {
2605
-        $relation_obj = $this->related_settings_for($relationName);
2606
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2607
-    }
2608
-
2609
-
2610
-    /**
2611
-     * @param mixed  $id_or_obj
2612
-     * @param string $relationName
2613
-     * @param array  $where_query_params
2614
-     * @param EE_Base_Class[] objects to which relations were removed
2615
-     * @return EE_Base_Class[]
2616
-     * @throws EE_Error
2617
-     */
2618
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2619
-    {
2620
-        $relation_obj = $this->related_settings_for($relationName);
2621
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2622
-    }
2623
-
2624
-
2625
-    /**
2626
-     * Gets all the related items of the specified $model_name, using $query_params.
2627
-     * Note: by default, we remove the "default query params"
2628
-     * because we want to get even deleted items etc.
2629
-     *
2630
-     * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2631
-     * @param string $model_name   like 'Event', 'Registration', etc. always singular
2632
-     * @param array  $query_params see github link below for more info
2633
-     * @return EE_Base_Class[]
2634
-     * @throws EE_Error
2635
-     * @throws ReflectionException
2636
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2637
-     */
2638
-    public function get_all_related($id_or_obj, $model_name, $query_params = null)
2639
-    {
2640
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2641
-        $relation_settings = $this->related_settings_for($model_name);
2642
-        return $relation_settings->get_all_related($model_obj, $query_params);
2643
-    }
2644
-
2645
-
2646
-    /**
2647
-     * Deletes all the model objects across the relation indicated by $model_name
2648
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2649
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2650
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2651
-     *
2652
-     * @param EE_Base_Class|int|string $id_or_obj
2653
-     * @param string                   $model_name
2654
-     * @param array                    $query_params
2655
-     * @return int how many deleted
2656
-     * @throws EE_Error
2657
-     * @throws ReflectionException
2658
-     */
2659
-    public function delete_related($id_or_obj, $model_name, $query_params = [])
2660
-    {
2661
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2662
-        $relation_settings = $this->related_settings_for($model_name);
2663
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2664
-    }
2665
-
2666
-
2667
-    /**
2668
-     * Hard deletes all the model objects across the relation indicated by $model_name
2669
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2670
-     * the model objects can't be hard deleted because of blocking related model objects,
2671
-     * just does a soft-delete on them instead.
2672
-     *
2673
-     * @param EE_Base_Class|int|string $id_or_obj
2674
-     * @param string                   $model_name
2675
-     * @param array                    $query_params
2676
-     * @return int how many deleted
2677
-     * @throws EE_Error
2678
-     * @throws ReflectionException
2679
-     */
2680
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2681
-    {
2682
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2683
-        $relation_settings = $this->related_settings_for($model_name);
2684
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2685
-    }
2686
-
2687
-
2688
-    /**
2689
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2690
-     * unless otherwise specified in the $query_params
2691
-     *
2692
-     * @param int             /EE_Base_Class $id_or_obj
2693
-     * @param string $model_name     like 'Event', or 'Registration'
2694
-     * @param array  $query_params
2695
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2696
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2697
-     *                               that by the setting $distinct to TRUE;
2698
-     * @return int
2699
-     * @throws EE_Error
2700
-     * @throws ReflectionException
2701
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2702
-     */
2703
-    public function count_related(
2704
-        $id_or_obj,
2705
-        $model_name,
2706
-        $query_params = [],
2707
-        $field_to_count = null,
2708
-        $distinct = false
2709
-    ) {
2710
-        $related_model = $this->get_related_model_obj($model_name);
2711
-        // we're just going to use the query params on the related model's normal get_all query,
2712
-        // except add a condition to say to match the current mod
2713
-        if (! isset($query_params['default_where_conditions'])) {
2714
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2715
-        }
2716
-        $this_model_name                                                 = $this->get_this_model_name();
2717
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2718
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2719
-        return $related_model->count($query_params, $field_to_count, $distinct);
2720
-    }
2721
-
2722
-
2723
-    /**
2724
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2725
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2726
-     *
2727
-     * @param int           /EE_Base_Class $id_or_obj
2728
-     * @param string $model_name   like 'Event', or 'Registration'
2729
-     * @param array  $query_params
2730
-     * @param string $field_to_sum name of field to count by. By default, uses primary key
2731
-     * @return float
2732
-     * @throws EE_Error
2733
-     * @throws ReflectionException
2734
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2735
-     */
2736
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2737
-    {
2738
-        $related_model = $this->get_related_model_obj($model_name);
2739
-        if (! is_array($query_params)) {
2740
-            EE_Error::doing_it_wrong(
2741
-                'EEM_Base::sum_related',
2742
-                sprintf(
2743
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2744
-                    gettype($query_params)
2745
-                ),
2746
-                '4.6.0'
2747
-            );
2748
-            $query_params = [];
2749
-        }
2750
-        // we're just going to use the query params on the related model's normal get_all query,
2751
-        // except add a condition to say to match the current mod
2752
-        if (! isset($query_params['default_where_conditions'])) {
2753
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2754
-        }
2755
-        $this_model_name                                                 = $this->get_this_model_name();
2756
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2757
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2758
-        return $related_model->sum($query_params, $field_to_sum);
2759
-    }
2760
-
2761
-
2762
-    /**
2763
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2764
-     * $modelObject
2765
-     *
2766
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2767
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2768
-     * @param array               $query_params     see github link below for more info
2769
-     * @return EE_Base_Class
2770
-     * @throws EE_Error
2771
-     * @throws ReflectionException
2772
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2773
-     */
2774
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2775
-    {
2776
-        $query_params['limit'] = 1;
2777
-        $results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2778
-        if ($results) {
2779
-            return array_shift($results);
2780
-        }
2781
-        return null;
2782
-    }
2783
-
2784
-
2785
-    /**
2786
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2787
-     *
2788
-     * @return string
2789
-     */
2790
-    public function get_this_model_name()
2791
-    {
2792
-        return str_replace("EEM_", "", get_class($this));
2793
-    }
2794
-
2795
-
2796
-    /**
2797
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2798
-     *
2799
-     * @return EE_Any_Foreign_Model_Name_Field
2800
-     * @throws EE_Error
2801
-     */
2802
-    public function get_field_containing_related_model_name()
2803
-    {
2804
-        foreach ($this->field_settings(true) as $field) {
2805
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2806
-                $field_with_model_name = $field;
2807
-            }
2808
-        }
2809
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2810
-            throw new EE_Error(
2811
-                sprintf(
2812
-                    esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2813
-                    $this->get_this_model_name()
2814
-                )
2815
-            );
2816
-        }
2817
-        return $field_with_model_name;
2818
-    }
2819
-
2820
-
2821
-    /**
2822
-     * Inserts a new entry into the database, for each table.
2823
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2824
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2825
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2826
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2827
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2828
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2829
-     *
2830
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2831
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2832
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2833
-     *                              of EEM_Base)
2834
-     * @return int|string new primary key on main table that got inserted
2835
-     * @throws EE_Error
2836
-     * @throws ReflectionException
2837
-     */
2838
-    public function insert($field_n_values)
2839
-    {
2840
-        /**
2841
-         * Filters the fields and their values before inserting an item using the models
2842
-         *
2843
-         * @param array    $fields_n_values keys are the fields and values are their new values
2844
-         * @param EEM_Base $model           the model used
2845
-         */
2846
-        $field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2847
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2848
-            $main_table = $this->_get_main_table();
2849
-            $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2850
-            if ($new_id !== false) {
2851
-                foreach ($this->_get_other_tables() as $other_table) {
2852
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2853
-                }
2854
-            }
2855
-            /**
2856
-             * Done just after attempting to insert a new model object
2857
-             *
2858
-             * @param EEM_Base $model           used
2859
-             * @param array    $fields_n_values fields and their values
2860
-             * @param int|string the              ID of the newly-inserted model object
2861
-             */
2862
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2863
-            return $new_id;
2864
-        }
2865
-        return false;
2866
-    }
2867
-
2868
-
2869
-    /**
2870
-     * Checks that the result would satisfy the unique indexes on this model
2871
-     *
2872
-     * @param array  $field_n_values
2873
-     * @param string $action
2874
-     * @return boolean
2875
-     * @throws EE_Error
2876
-     * @throws ReflectionException
2877
-     */
2878
-    protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2879
-    {
2880
-        foreach ($this->unique_indexes() as $index_name => $index) {
2881
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2882
-            if ($this->exists([$uniqueness_where_params])) {
2883
-                EE_Error::add_error(
2884
-                    sprintf(
2885
-                        esc_html__(
2886
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2887
-                            "event_espresso"
2888
-                        ),
2889
-                        $action,
2890
-                        $this->_get_class_name(),
2891
-                        $index_name,
2892
-                        implode(",", $index->field_names()),
2893
-                        http_build_query($uniqueness_where_params)
2894
-                    ),
2895
-                    __FILE__,
2896
-                    __FUNCTION__,
2897
-                    __LINE__
2898
-                );
2899
-                return false;
2900
-            }
2901
-        }
2902
-        return true;
2903
-    }
2904
-
2905
-
2906
-    /**
2907
-     * Checks the database for an item that conflicts (ie, if this item were
2908
-     * saved to the DB would break some uniqueness requirement, like a primary key
2909
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
2910
-     * can be either an EE_Base_Class or an array of fields n values
2911
-     *
2912
-     * @param EE_Base_Class|array $obj_or_fields_array
2913
-     * @param boolean             $include_primary_key whether to use the model object's primary key
2914
-     *                                                 when looking for conflicts
2915
-     *                                                 (ie, if false, we ignore the model object's primary key
2916
-     *                                                 when finding "conflicts". If true, it's also considered).
2917
-     *                                                 Only works for INT primary key,
2918
-     *                                                 STRING primary keys cannot be ignored
2919
-     * @return EE_Base_Class|array
2920
-     * @throws EE_Error
2921
-     * @throws ReflectionException
2922
-     */
2923
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2924
-    {
2925
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
2926
-            $fields_n_values = $obj_or_fields_array->model_field_array();
2927
-        } elseif (is_array($obj_or_fields_array)) {
2928
-            $fields_n_values = $obj_or_fields_array;
2929
-        } else {
2930
-            throw new EE_Error(
2931
-                sprintf(
2932
-                    esc_html__(
2933
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2934
-                        "event_espresso"
2935
-                    ),
2936
-                    get_class($this),
2937
-                    $obj_or_fields_array
2938
-                )
2939
-            );
2940
-        }
2941
-        $query_params = [];
2942
-        if (
2943
-            $this->has_primary_key_field()
2944
-            && ($include_primary_key
2945
-                || $this->get_primary_key_field()
2946
-                   instanceof
2947
-                   EE_Primary_Key_String_Field)
2948
-            && isset($fields_n_values[ $this->primary_key_name() ])
2949
-        ) {
2950
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2951
-        }
2952
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2953
-            $uniqueness_where_params                              =
2954
-                array_intersect_key($fields_n_values, $unique_index->fields());
2955
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2956
-        }
2957
-        // if there is nothing to base this search on, then we shouldn't find anything
2958
-        if (empty($query_params)) {
2959
-            return [];
2960
-        }
2961
-        return $this->get_one($query_params);
2962
-    }
2963
-
2964
-
2965
-    /**
2966
-     * Like count, but is optimized and returns a boolean instead of an int
2967
-     *
2968
-     * @param array $query_params
2969
-     * @return boolean
2970
-     * @throws EE_Error
2971
-     * @throws ReflectionException
2972
-     */
2973
-    public function exists($query_params)
2974
-    {
2975
-        $query_params['limit'] = 1;
2976
-        return $this->count($query_params) > 0;
2977
-    }
2978
-
2979
-
2980
-    /**
2981
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
2982
-     *
2983
-     * @param int|string $id
2984
-     * @return boolean
2985
-     * @throws EE_Error
2986
-     * @throws ReflectionException
2987
-     */
2988
-    public function exists_by_ID($id)
2989
-    {
2990
-        return $this->exists(
2991
-            [
2992
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
2993
-                [
2994
-                    $this->primary_key_name() => $id,
2995
-                ],
2996
-            ]
2997
-        );
2998
-    }
2999
-
3000
-
3001
-    /**
3002
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3003
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3004
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3005
-     * on the main table)
3006
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3007
-     * cases where we want to call it directly rather than via insert().
3008
-     *
3009
-     * @access   protected
3010
-     * @param EE_Table_Base $table
3011
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3012
-     *                                       float
3013
-     * @param int           $new_id          for now we assume only int keys
3014
-     * @return int ID of new row inserted, or FALSE on failure
3015
-     * @throws EE_Error
3016
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3017
-     */
3018
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3019
-    {
3020
-        global $wpdb;
3021
-        $insertion_col_n_values = [];
3022
-        $format_for_insertion   = [];
3023
-        $fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3024
-        foreach ($fields_on_table as $field_name => $field_obj) {
3025
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3026
-            if ($field_obj->is_auto_increment()) {
3027
-                continue;
3028
-            }
3029
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3030
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3031
-            if ($prepared_value !== null) {
3032
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3033
-                $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3034
-            }
3035
-        }
3036
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3037
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3038
-            // so add the fk to the main table as a column
3039
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3040
-            $format_for_insertion[]                              =
3041
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3042
-        }
3043
-        // insert the new entry
3044
-        $result = $this->_do_wpdb_query(
3045
-            'insert',
3046
-            [$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3047
-        );
3048
-        if ($result === false) {
3049
-            return false;
3050
-        }
3051
-        // ok, now what do we return for the ID of the newly-inserted thing?
3052
-        if ($this->has_primary_key_field()) {
3053
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3054
-                return $wpdb->insert_id;
3055
-            }
3056
-            // it's not an auto-increment primary key, so
3057
-            // it must have been supplied
3058
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3059
-        }
3060
-        // we can't return a  primary key because there is none. instead return
3061
-        // a unique string indicating this model
3062
-        return $this->get_index_primary_key_string($fields_n_values);
3063
-    }
3064
-
3065
-
3066
-    /**
3067
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3068
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3069
-     * and there is no default, we pass it along. WPDB will take care of it)
3070
-     *
3071
-     * @param EE_Model_Field_Base $field_obj
3072
-     * @param array               $fields_n_values
3073
-     * @return mixed string|int|float depending on what the table column will be expecting
3074
-     * @throws EE_Error
3075
-     */
3076
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3077
-    {
3078
-        // if this field doesn't allow nullable, don't allow it
3079
-        if (
3080
-            ! $field_obj->is_nullable()
3081
-            && (
3082
-                ! isset($fields_n_values[ $field_obj->get_name() ])
3083
-                || $fields_n_values[ $field_obj->get_name() ] === null
3084
-            )
3085
-        ) {
3086
-            $fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3087
-        }
3088
-        $unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3089
-            ? $fields_n_values[ $field_obj->get_name() ]
3090
-            : null;
3091
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3092
-    }
3093
-
3094
-
3095
-    /**
3096
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3097
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3098
-     * the field's prepare_for_set() method.
3099
-     *
3100
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3101
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3102
-     *                                   top of file)
3103
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3104
-     *                                   $value is a custom selection
3105
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3106
-     */
3107
-    private function _prepare_value_for_use_in_db($value, $field)
3108
-    {
3109
-        if ($field && $field instanceof EE_Model_Field_Base) {
3110
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3111
-            switch ($this->_values_already_prepared_by_model_object) {
3112
-                /** @noinspection PhpMissingBreakStatementInspection */
3113
-                case self::not_prepared_by_model_object:
3114
-                    $value = $field->prepare_for_set($value);
3115
-                // purposefully left out "return"
3116
-                case self::prepared_by_model_object:
3117
-                    /** @noinspection SuspiciousAssignmentsInspection */
3118
-                    $value = $field->prepare_for_use_in_db($value);
3119
-                case self::prepared_for_use_in_db:
3120
-                    // leave the value alone
3121
-            }
3122
-            return $value;
3123
-            // phpcs:enable
3124
-        }
3125
-        return $value;
3126
-    }
3127
-
3128
-
3129
-    /**
3130
-     * Returns the main table on this model
3131
-     *
3132
-     * @return EE_Primary_Table
3133
-     * @throws EE_Error
3134
-     */
3135
-    protected function _get_main_table()
3136
-    {
3137
-        foreach ($this->_tables as $table) {
3138
-            if ($table instanceof EE_Primary_Table) {
3139
-                return $table;
3140
-            }
3141
-        }
3142
-        throw new EE_Error(
3143
-            sprintf(
3144
-                esc_html__(
3145
-                    'There are no main tables on %s. They should be added to _tables array in the constructor',
3146
-                    'event_espresso'
3147
-                ),
3148
-                get_class($this)
3149
-            )
3150
-        );
3151
-    }
3152
-
3153
-
3154
-    /**
3155
-     * table
3156
-     * returns EE_Primary_Table table name
3157
-     *
3158
-     * @return string
3159
-     * @throws EE_Error
3160
-     */
3161
-    public function table()
3162
-    {
3163
-        return $this->_get_main_table()->get_table_name();
3164
-    }
3165
-
3166
-
3167
-    /**
3168
-     * table
3169
-     * returns first EE_Secondary_Table table name
3170
-     *
3171
-     * @return string
3172
-     */
3173
-    public function second_table()
3174
-    {
3175
-        // grab second table from tables array
3176
-        $second_table = end($this->_tables);
3177
-        return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3178
-    }
3179
-
3180
-
3181
-    /**
3182
-     * get_table_obj_by_alias
3183
-     * returns table name given it's alias
3184
-     *
3185
-     * @param string $table_alias
3186
-     * @return EE_Primary_Table | EE_Secondary_Table
3187
-     */
3188
-    public function get_table_obj_by_alias($table_alias = '')
3189
-    {
3190
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3191
-    }
3192
-
3193
-
3194
-    /**
3195
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3196
-     *
3197
-     * @return EE_Secondary_Table[]
3198
-     */
3199
-    protected function _get_other_tables()
3200
-    {
3201
-        $other_tables = [];
3202
-        foreach ($this->_tables as $table_alias => $table) {
3203
-            if ($table instanceof EE_Secondary_Table) {
3204
-                $other_tables[ $table_alias ] = $table;
3205
-            }
3206
-        }
3207
-        return $other_tables;
3208
-    }
3209
-
3210
-
3211
-    /**
3212
-     * Finds all the fields that correspond to the given table
3213
-     *
3214
-     * @param string $table_alias , array key in EEM_Base::_tables
3215
-     * @return EE_Model_Field_Base[]
3216
-     */
3217
-    public function _get_fields_for_table($table_alias)
3218
-    {
3219
-        return $this->_fields[ $table_alias ];
3220
-    }
3221
-
3222
-
3223
-    /**
3224
-     * Recurses through all the where parameters, and finds all the related models we'll need
3225
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3226
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3227
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3228
-     * related Registration, Transaction, and Payment models.
3229
-     *
3230
-     * @param array $query_params see github link below for more info
3231
-     * @return EE_Model_Query_Info_Carrier
3232
-     * @throws EE_Error
3233
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3234
-     */
3235
-    public function _extract_related_models_from_query($query_params)
3236
-    {
3237
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3238
-        if (array_key_exists(0, $query_params)) {
3239
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3240
-        }
3241
-        if (array_key_exists('group_by', $query_params)) {
3242
-            if (is_array($query_params['group_by'])) {
3243
-                $this->_extract_related_models_from_sub_params_array_values(
3244
-                    $query_params['group_by'],
3245
-                    $query_info_carrier,
3246
-                    'group_by'
3247
-                );
3248
-            } elseif (! empty($query_params['group_by'])) {
3249
-                $this->_extract_related_model_info_from_query_param(
3250
-                    $query_params['group_by'],
3251
-                    $query_info_carrier,
3252
-                    'group_by'
3253
-                );
3254
-            }
3255
-        }
3256
-        if (array_key_exists('having', $query_params)) {
3257
-            $this->_extract_related_models_from_sub_params_array_keys(
3258
-                $query_params[0],
3259
-                $query_info_carrier,
3260
-                'having'
3261
-            );
3262
-        }
3263
-        if (array_key_exists('order_by', $query_params)) {
3264
-            if (is_array($query_params['order_by'])) {
3265
-                $this->_extract_related_models_from_sub_params_array_keys(
3266
-                    $query_params['order_by'],
3267
-                    $query_info_carrier,
3268
-                    'order_by'
3269
-                );
3270
-            } elseif (! empty($query_params['order_by'])) {
3271
-                $this->_extract_related_model_info_from_query_param(
3272
-                    $query_params['order_by'],
3273
-                    $query_info_carrier,
3274
-                    'order_by'
3275
-                );
3276
-            }
3277
-        }
3278
-        if (array_key_exists('force_join', $query_params)) {
3279
-            $this->_extract_related_models_from_sub_params_array_values(
3280
-                $query_params['force_join'],
3281
-                $query_info_carrier,
3282
-                'force_join'
3283
-            );
3284
-        }
3285
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3286
-        return $query_info_carrier;
3287
-    }
3288
-
3289
-
3290
-    /**
3291
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3292
-     *
3293
-     * @param array                       $sub_query_params
3294
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3295
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3296
-     * @return EE_Model_Query_Info_Carrier
3297
-     * @throws EE_Error
3298
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3299
-     */
3300
-    private function _extract_related_models_from_sub_params_array_keys(
3301
-        $sub_query_params,
3302
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3303
-        $query_param_type
3304
-    ) {
3305
-        if (! empty($sub_query_params)) {
3306
-            $sub_query_params = (array)$sub_query_params;
3307
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3308
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3309
-                $this->_extract_related_model_info_from_query_param(
3310
-                    $param,
3311
-                    $model_query_info_carrier,
3312
-                    $query_param_type
3313
-                );
3314
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3315
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3316
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3317
-                // of array('Registration.TXN_ID'=>23)
3318
-                $query_param_sans_stars =
3319
-                    $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3320
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3321
-                    if (! is_array($possibly_array_of_params)) {
3322
-                        throw new EE_Error(
3323
-                            sprintf(
3324
-                                esc_html__(
3325
-                                    "You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3326
-                                    "event_espresso"
3327
-                                ),
3328
-                                $param,
3329
-                                $possibly_array_of_params
3330
-                            )
3331
-                        );
3332
-                    }
3333
-                    $this->_extract_related_models_from_sub_params_array_keys(
3334
-                        $possibly_array_of_params,
3335
-                        $model_query_info_carrier,
3336
-                        $query_param_type
3337
-                    );
3338
-                } elseif (
3339
-                    $query_param_type === 0 // ie WHERE
3340
-                    && is_array($possibly_array_of_params)
3341
-                    && isset($possibly_array_of_params[2])
3342
-                    && $possibly_array_of_params[2] == true
3343
-                ) {
3344
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3345
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3346
-                    // from which we should extract query parameters!
3347
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3348
-                        throw new EE_Error(
3349
-                            sprintf(
3350
-                                esc_html__(
3351
-                                    "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3352
-                                    "event_espresso"
3353
-                                ),
3354
-                                $query_param_type,
3355
-                                implode(",", $possibly_array_of_params)
3356
-                            )
3357
-                        );
3358
-                    }
3359
-                    $this->_extract_related_model_info_from_query_param(
3360
-                        $possibly_array_of_params[1],
3361
-                        $model_query_info_carrier,
3362
-                        $query_param_type
3363
-                    );
3364
-                }
3365
-            }
3366
-        }
3367
-        return $model_query_info_carrier;
3368
-    }
3369
-
3370
-
3371
-    /**
3372
-     * For extracting related models from forced_joins, where the array values contain the info about what
3373
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3374
-     *
3375
-     * @param array                       $sub_query_params
3376
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3377
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3378
-     * @return EE_Model_Query_Info_Carrier
3379
-     * @throws EE_Error
3380
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3381
-     */
3382
-    private function _extract_related_models_from_sub_params_array_values(
3383
-        $sub_query_params,
3384
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3385
-        $query_param_type
3386
-    ) {
3387
-        if (! empty($sub_query_params)) {
3388
-            if (! is_array($sub_query_params)) {
3389
-                throw new EE_Error(
3390
-                    sprintf(
3391
-                        esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3392
-                        $sub_query_params
3393
-                    )
3394
-                );
3395
-            }
3396
-            foreach ($sub_query_params as $param) {
3397
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3398
-                $this->_extract_related_model_info_from_query_param(
3399
-                    $param,
3400
-                    $model_query_info_carrier,
3401
-                    $query_param_type
3402
-                );
3403
-            }
3404
-        }
3405
-        return $model_query_info_carrier;
3406
-    }
3407
-
3408
-
3409
-    /**
3410
-     * Extract all the query parts from  model query params
3411
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3412
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3413
-     * but use them in a different order. Eg, we need to know what models we are querying
3414
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3415
-     * other models before we can finalize the where clause SQL.
3416
-     *
3417
-     * @param array $query_params see github link below for more info
3418
-     * @return EE_Model_Query_Info_Carrier
3419
-     * @throws EE_Error
3420
-     * @throws ModelConfigurationException
3421
-     * @throws ReflectionException
3422
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3423
-     */
3424
-    public function _create_model_query_info_carrier($query_params)
3425
-    {
3426
-        if (! is_array($query_params)) {
3427
-            EE_Error::doing_it_wrong(
3428
-                'EEM_Base::_create_model_query_info_carrier',
3429
-                sprintf(
3430
-                    esc_html__(
3431
-                        '$query_params should be an array, you passed a variable of type %s',
3432
-                        'event_espresso'
3433
-                    ),
3434
-                    gettype($query_params)
3435
-                ),
3436
-                '4.6.0'
3437
-            );
3438
-            $query_params = [];
3439
-        }
3440
-        $query_params[0] = isset($query_params[0]) ? $query_params[0] : [];
3441
-        // first check if we should alter the query to account for caps or not
3442
-        // because the caps might require us to do extra joins
3443
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3444
-            $query_params[0] = array_replace_recursive(
3445
-                $query_params[0],
3446
-                $this->caps_where_conditions(
3447
-                    $query_params['caps']
3448
-                )
3449
-            );
3450
-        }
3451
-
3452
-        // check if we should alter the query to remove data related to protected
3453
-        // custom post types
3454
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3455
-            $where_param_key_for_password = $this->modelChainAndPassword();
3456
-            // only include if related to a cpt where no password has been set
3457
-            $query_params[0]['OR*nopassword'] = [
3458
-                $where_param_key_for_password       => '',
3459
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3460
-            ];
3461
-        }
3462
-        $query_object = $this->_extract_related_models_from_query($query_params);
3463
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3464
-        foreach ($query_params[0] as $key => $value) {
3465
-            if (is_int($key)) {
3466
-                throw new EE_Error(
3467
-                    sprintf(
3468
-                        esc_html__(
3469
-                            "WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3470
-                            "event_espresso"
3471
-                        ),
3472
-                        $key,
3473
-                        var_export($value, true),
3474
-                        var_export($query_params, true),
3475
-                        get_class($this)
3476
-                    )
3477
-                );
3478
-            }
3479
-        }
3480
-        if (
3481
-            array_key_exists('default_where_conditions', $query_params)
3482
-            && ! empty($query_params['default_where_conditions'])
3483
-        ) {
3484
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3485
-        } else {
3486
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3487
-        }
3488
-        $query_params[0] = array_merge(
3489
-            $this->_get_default_where_conditions_for_models_in_query(
3490
-                $query_object,
3491
-                $use_default_where_conditions,
3492
-                $query_params[0]
3493
-            ),
3494
-            $query_params[0]
3495
-        );
3496
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3497
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3498
-        // So we need to setup a subquery and use that for the main join.
3499
-        // Note for now this only works on the primary table for the model.
3500
-        // So for instance, you could set the limit array like this:
3501
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3502
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3503
-            $query_object->set_main_model_join_sql(
3504
-                $this->_construct_limit_join_select(
3505
-                    $query_params['on_join_limit'][0],
3506
-                    $query_params['on_join_limit'][1]
3507
-                )
3508
-            );
3509
-        }
3510
-        // set limit
3511
-        if (array_key_exists('limit', $query_params)) {
3512
-            if (is_array($query_params['limit'])) {
3513
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3514
-                    $e = sprintf(
3515
-                        esc_html__(
3516
-                            "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3517
-                            "event_espresso"
3518
-                        ),
3519
-                        http_build_query($query_params['limit'])
3520
-                    );
3521
-                    throw new EE_Error($e . "|" . $e);
3522
-                }
3523
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3524
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3525
-            } elseif (! empty($query_params['limit'])) {
3526
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3527
-            }
3528
-        }
3529
-        // set order by
3530
-        if (array_key_exists('order_by', $query_params)) {
3531
-            if (is_array($query_params['order_by'])) {
3532
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3533
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3534
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3535
-                if (array_key_exists('order', $query_params)) {
3536
-                    throw new EE_Error(
3537
-                        sprintf(
3538
-                            esc_html__(
3539
-                                "In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3540
-                                "event_espresso"
3541
-                            ),
3542
-                            get_class($this),
3543
-                            implode(", ", array_keys($query_params['order_by'])),
3544
-                            implode(", ", $query_params['order_by']),
3545
-                            $query_params['order']
3546
-                        )
3547
-                    );
3548
-                }
3549
-                $this->_extract_related_models_from_sub_params_array_keys(
3550
-                    $query_params['order_by'],
3551
-                    $query_object,
3552
-                    'order_by'
3553
-                );
3554
-                // assume it's an array of fields to order by
3555
-                $order_array = [];
3556
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3557
-                    $order         = $this->_extract_order($order);
3558
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3559
-                }
3560
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3561
-            } elseif (! empty($query_params['order_by'])) {
3562
-                $this->_extract_related_model_info_from_query_param(
3563
-                    $query_params['order_by'],
3564
-                    $query_object,
3565
-                    'order',
3566
-                    $query_params['order_by']
3567
-                );
3568
-                $order = isset($query_params['order'])
3569
-                    ? $this->_extract_order($query_params['order'])
3570
-                    : 'DESC';
3571
-                $query_object->set_order_by_sql(
3572
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3573
-                );
3574
-            }
3575
-        }
3576
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3577
-        if (
3578
-            ! array_key_exists('order_by', $query_params)
3579
-            && array_key_exists('order', $query_params)
3580
-            && ! empty($query_params['order'])
3581
-        ) {
3582
-            $pk_field = $this->get_primary_key_field();
3583
-            $order    = $this->_extract_order($query_params['order']);
3584
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3585
-        }
3586
-        // set group by
3587
-        if (array_key_exists('group_by', $query_params)) {
3588
-            if (is_array($query_params['group_by'])) {
3589
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3590
-                $group_by_array = [];
3591
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3592
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3593
-                }
3594
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3595
-            } elseif (! empty($query_params['group_by'])) {
3596
-                $query_object->set_group_by_sql(
3597
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3598
-                );
3599
-            }
3600
-        }
3601
-        // set having
3602
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3603
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3604
-        }
3605
-        // now, just verify they didn't pass anything wack
3606
-        foreach ($query_params as $query_key => $query_value) {
3607
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3608
-                throw new EE_Error(
3609
-                    sprintf(
3610
-                        esc_html__(
3611
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3612
-                            'event_espresso'
3613
-                        ),
3614
-                        $query_key,
3615
-                        get_class($this),
3616
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3617
-                        implode(',', $this->_allowed_query_params)
3618
-                    )
3619
-                );
3620
-            }
3621
-        }
3622
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3623
-        if (empty($main_model_join_sql)) {
3624
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3625
-        }
3626
-        return $query_object;
3627
-    }
3628
-
3629
-
3630
-    /**
3631
-     * Gets the where conditions that should be imposed on the query based on the
3632
-     * context (eg reading frontend, backend, edit or delete).
3633
-     *
3634
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3635
-     * @return array
3636
-     * @throws EE_Error
3637
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3638
-     */
3639
-    public function caps_where_conditions($context = self::caps_read)
3640
-    {
3641
-        EEM_Base::verify_is_valid_cap_context($context);
3642
-        $cap_where_conditions = [];
3643
-        $cap_restrictions     = $this->caps_missing($context);
3644
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3645
-            $cap_where_conditions = array_replace_recursive(
3646
-                $cap_where_conditions,
3647
-                $restriction_if_no_cap->get_default_where_conditions()
3648
-            );
3649
-        }
3650
-        return apply_filters(
3651
-            'FHEE__EEM_Base__caps_where_conditions__return',
3652
-            $cap_where_conditions,
3653
-            $this,
3654
-            $context,
3655
-            $cap_restrictions
3656
-        );
3657
-    }
3658
-
3659
-
3660
-    /**
3661
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3662
-     * otherwise throws an exception
3663
-     *
3664
-     * @param string $should_be_order_string
3665
-     * @return string either ASC, asc, DESC or desc
3666
-     * @throws EE_Error
3667
-     */
3668
-    private function _extract_order($should_be_order_string)
3669
-    {
3670
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3671
-            return $should_be_order_string;
3672
-        }
3673
-        throw new EE_Error(
3674
-            sprintf(
3675
-                esc_html__(
3676
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3677
-                    "event_espresso"
3678
-                ),
3679
-                get_class($this),
3680
-                $should_be_order_string
3681
-            )
3682
-        );
3683
-    }
3684
-
3685
-
3686
-    /**
3687
-     * Looks at all the models which are included in this query, and asks each
3688
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3689
-     * so they can be merged
3690
-     *
3691
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3692
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3693
-     *                                                                  'none' means NO default where conditions will
3694
-     *                                                                  be used AT ALL during this query.
3695
-     *                                                                  'other_models_only' means default where
3696
-     *                                                                  conditions from other models will be used, but
3697
-     *                                                                  not for this primary model. 'all', the default,
3698
-     *                                                                  means default where conditions will apply as
3699
-     *                                                                  normal
3700
-     * @param array                       $where_query_params
3701
-     * @return array
3702
-     * @throws EE_Error
3703
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3704
-     */
3705
-    private function _get_default_where_conditions_for_models_in_query(
3706
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3707
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3708
-        $where_query_params = []
3709
-    ) {
3710
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3711
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3712
-            throw new EE_Error(
3713
-                sprintf(
3714
-                    esc_html__(
3715
-                        "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3716
-                        "event_espresso"
3717
-                    ),
3718
-                    $use_default_where_conditions,
3719
-                    implode(", ", $allowed_used_default_where_conditions_values)
3720
-                )
3721
-            );
3722
-        }
3723
-        $universal_query_params = [];
3724
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions)) {
3725
-            $universal_query_params = $this->_get_default_where_conditions();
3726
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions)) {
3727
-            $universal_query_params = $this->_get_minimum_where_conditions();
3728
-        }
3729
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3730
-            $related_model = $this->get_related_model_obj($model_name);
3731
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3732
-                $related_model_universal_where_params =
3733
-                    $related_model->_get_default_where_conditions($model_relation_path);
3734
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3735
-                $related_model_universal_where_params =
3736
-                    $related_model->_get_minimum_where_conditions($model_relation_path);
3737
-            } else {
3738
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3739
-                continue;
3740
-            }
3741
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3742
-                $related_model_universal_where_params,
3743
-                $where_query_params,
3744
-                $related_model,
3745
-                $model_relation_path
3746
-            );
3747
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3748
-                $universal_query_params,
3749
-                $overrides
3750
-            );
3751
-        }
3752
-        return $universal_query_params;
3753
-    }
3754
-
3755
-
3756
-    /**
3757
-     * Determines whether or not we should use default where conditions for the model in question
3758
-     * (this model, or other related models).
3759
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3760
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3761
-     * We should use default where conditions on related models when they requested to use default where conditions
3762
-     * on all models, or specifically just on other related models
3763
-     *
3764
-     * @param      $default_where_conditions_value
3765
-     * @param bool $for_this_model false means this is for OTHER related models
3766
-     * @return bool
3767
-     */
3768
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3769
-    {
3770
-        return (
3771
-                   $for_this_model
3772
-                   && in_array(
3773
-                       $default_where_conditions_value,
3774
-                       [
3775
-                           EEM_Base::default_where_conditions_all,
3776
-                           EEM_Base::default_where_conditions_this_only,
3777
-                           EEM_Base::default_where_conditions_minimum_others,
3778
-                       ],
3779
-                       true
3780
-                   )
3781
-               )
3782
-               || (
3783
-                   ! $for_this_model
3784
-                   && in_array(
3785
-                       $default_where_conditions_value,
3786
-                       [
3787
-                           EEM_Base::default_where_conditions_all,
3788
-                           EEM_Base::default_where_conditions_others_only,
3789
-                       ],
3790
-                       true
3791
-                   )
3792
-               );
3793
-    }
3794
-
3795
-
3796
-    /**
3797
-     * Determines whether or not we should use default minimum conditions for the model in question
3798
-     * (this model, or other related models).
3799
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3800
-     * where conditions.
3801
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3802
-     * on this model or others
3803
-     *
3804
-     * @param      $default_where_conditions_value
3805
-     * @param bool $for_this_model false means this is for OTHER related models
3806
-     * @return bool
3807
-     */
3808
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3809
-    {
3810
-        return (
3811
-                   $for_this_model
3812
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3813
-               )
3814
-               || (
3815
-                   ! $for_this_model
3816
-                   && in_array(
3817
-                       $default_where_conditions_value,
3818
-                       [
3819
-                           EEM_Base::default_where_conditions_minimum_others,
3820
-                           EEM_Base::default_where_conditions_minimum_all,
3821
-                       ],
3822
-                       true
3823
-                   )
3824
-               );
3825
-    }
3826
-
3827
-
3828
-    /**
3829
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3830
-     * then we also add a special where condition which allows for that model's primary key
3831
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3832
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3833
-     *
3834
-     * @param array    $default_where_conditions
3835
-     * @param array    $provided_where_conditions
3836
-     * @param EEM_Base $model
3837
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3838
-     * @return array
3839
-     * @throws EE_Error
3840
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3841
-     */
3842
-    private function _override_defaults_or_make_null_friendly(
3843
-        $default_where_conditions,
3844
-        $provided_where_conditions,
3845
-        $model,
3846
-        $model_relation_path
3847
-    ) {
3848
-        $null_friendly_where_conditions = [];
3849
-        $none_overridden                = true;
3850
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3851
-        foreach ($default_where_conditions as $key => $val) {
3852
-            if (isset($provided_where_conditions[ $key ])) {
3853
-                $none_overridden = false;
3854
-            } else {
3855
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3856
-            }
3857
-        }
3858
-        if ($none_overridden && $default_where_conditions) {
3859
-            if ($model->has_primary_key_field()) {
3860
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3861
-                                                                                   . "."
3862
-                                                                                   . $model->primary_key_name() ] =
3863
-                    ['IS NULL'];
3864
-            }/*else{
40
+	/**
41
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
42
+	 */
43
+	const caps_read       = 'read';
44
+
45
+	const caps_read_admin = 'read_admin';
46
+
47
+	const caps_edit       = 'edit';
48
+
49
+	const caps_delete     = 'delete';
50
+
51
+
52
+	/**
53
+	 * constant used to show EEM_Base has not yet verified the db on this http request
54
+	 */
55
+	const db_verified_none = 0;
56
+
57
+	/**
58
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
59
+	 * but not the addons' dbs
60
+	 */
61
+	const db_verified_core = 1;
62
+
63
+	/**
64
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
65
+	 * the EE core db too)
66
+	 */
67
+	const db_verified_addons = 2;
68
+
69
+	/**
70
+	 * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
71
+	 *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
72
+	 *        registrations for non-trashed tickets for non-trashed datetimes)
73
+	 */
74
+	const default_where_conditions_all = 'all';
75
+
76
+	/**
77
+	 * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
78
+	 *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
79
+	 *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
80
+	 *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
81
+	 *        models which share tables with other models, this can return data for the wrong model.
82
+	 */
83
+	const default_where_conditions_this_only = 'this_model_only';
84
+
85
+	/**
86
+	 * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
87
+	 *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
88
+	 *        return all registrations related to non-trashed tickets and non-trashed datetimes)
89
+	 */
90
+	const default_where_conditions_others_only = 'other_models_only';
91
+
92
+	/**
93
+	 * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
94
+	 *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
95
+	 *        their table with other models, like the Event and Venue models. For example, when querying for events
96
+	 *        ordered by their venues' name, this will be sure to only return real events with associated real venues
97
+	 *        (regardless of whether those events and venues are trashed)
98
+	 *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
99
+	 *        events.
100
+	 */
101
+	const default_where_conditions_minimum_all = 'minimum';
102
+
103
+	/**
104
+	 * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
105
+	 *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
106
+	 *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
107
+	 *        not)
108
+	 */
109
+	const default_where_conditions_minimum_others = 'full_this_minimum_others';
110
+
111
+	/**
112
+	 * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
113
+	 *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
114
+	 *        it's possible it will return table entries for other models. You should use
115
+	 *        EEM_Base::default_where_conditions_minimum_all instead.
116
+	 */
117
+	const default_where_conditions_none = 'none';
118
+
119
+	/**
120
+	 * when $_values_already_prepared_by_model_object equals this, we assume
121
+	 * the data is just like form input that needs to have the model fields'
122
+	 * prepare_for_set and prepare_for_use_in_db called on it
123
+	 */
124
+	const not_prepared_by_model_object = 0;
125
+
126
+	/**
127
+	 * when $_values_already_prepared_by_model_object equals this, we
128
+	 * assume this value is coming from a model object and doesn't need to have
129
+	 * prepare_for_set called on it, just prepare_for_use_in_db is used
130
+	 */
131
+	const prepared_by_model_object = 1;
132
+
133
+	/**
134
+	 * when $_values_already_prepared_by_model_object equals this, we assume
135
+	 * the values are already to be used in the database (ie no processing is done
136
+	 * on them by the model's fields)
137
+	 */
138
+	const prepared_for_use_in_db = 2;
139
+
140
+	/**
141
+	 * Flag to indicate whether the values provided to EEM_Base have already been prepared
142
+	 * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
143
+	 * They almost always WILL NOT, but it's not necessarily a requirement.
144
+	 * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
145
+	 *
146
+	 * @var boolean
147
+	 */
148
+	private $_values_already_prepared_by_model_object = 0;
149
+
150
+
151
+	/**
152
+	 * @var string
153
+	 */
154
+	protected $singular_item = 'Item';
155
+
156
+	/**
157
+	 * @var string
158
+	 */
159
+	protected $plural_item = 'Items';
160
+
161
+	/**
162
+	 * array of EE_Table objects for defining which tables comprise this model.
163
+	 *
164
+	 * @type EE_Table_Base[] $_tables
165
+	 */
166
+	protected $_tables;
167
+
168
+	/**
169
+	 * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
170
+	 * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
171
+	 * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
172
+	 *
173
+	 * @var EE_Model_Field_Base[][] $_fields
174
+	 */
175
+	protected $_fields;
176
+
177
+	/**
178
+	 * array of different kinds of relations
179
+	 *
180
+	 * @var EE_Model_Relation_Base[] $_model_relations
181
+	 */
182
+	protected $_model_relations;
183
+
184
+	/**
185
+	 * @var EE_Index[] $_indexes
186
+	 */
187
+	protected $_indexes = [];
188
+
189
+	/**
190
+	 * Default strategy for getting where conditions on this model. This strategy is used to get default
191
+	 * where conditions which are added to get_all, update, and delete queries. They can be overridden
192
+	 * by setting the same columns as used in these queries in the query yourself.
193
+	 *
194
+	 * @var EE_Default_Where_Conditions
195
+	 */
196
+	protected $_default_where_conditions_strategy;
197
+
198
+	/**
199
+	 * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
200
+	 * This is particularly useful when you want something between 'none' and 'default'
201
+	 *
202
+	 * @var EE_Default_Where_Conditions
203
+	 */
204
+	protected $_minimum_where_conditions_strategy;
205
+
206
+	/**
207
+	 * String describing how to find the "owner" of this model's objects.
208
+	 * When there is a foreign key on this model to the wp_users table, this isn't needed.
209
+	 * But when there isn't, this indicates which related model, or transiently-related model,
210
+	 * has the foreign key to the wp_users table.
211
+	 * Eg, for EEM_Registration this would be 'Event' because registrations are directly
212
+	 * related to events, and events have a foreign key to wp_users.
213
+	 * On EEM_Transaction, this would be 'Transaction.Event'
214
+	 *
215
+	 * @var string
216
+	 */
217
+	protected $_model_chain_to_wp_user = '';
218
+
219
+	/**
220
+	 * String describing how to find the model with a password controlling access to this model. This property has the
221
+	 * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
222
+	 * This value is the path of models to follow to arrive at the model with the password field.
223
+	 * If it is an empty string, it means this model has the password field. If it is null, it means there is no
224
+	 * model with a password that should affect reading this on the front-end.
225
+	 * Eg this is an empty string for the Event model because it has a password.
226
+	 * This is null for the Registration model, because its event's password has no bearing on whether
227
+	 * you can read the registration or not on the front-end (it just depends on your capabilities.)
228
+	 * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
229
+	 * should hide tickets for datetimes for events that have a password set.
230
+	 *
231
+	 * @var string |null
232
+	 */
233
+	protected $model_chain_to_password = null;
234
+
235
+	/**
236
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
237
+	 * don't need it (particularly CPT models)
238
+	 *
239
+	 * @var bool
240
+	 */
241
+	protected $_ignore_where_strategy = false;
242
+
243
+	/**
244
+	 * String used in caps relating to this model. Eg, if the caps relating to this
245
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
246
+	 *
247
+	 * @var string. If null it hasn't been initialized yet. If false then we
248
+	 * have indicated capabilities don't apply to this
249
+	 */
250
+	protected $_caps_slug = null;
251
+
252
+	/**
253
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
254
+	 * and next-level keys are capability names, and values are a
255
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
256
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
257
+	 * and then each capability in the corresponding sub-array that they're missing
258
+	 * adds the where conditions onto the query.
259
+	 *
260
+	 * @var array
261
+	 */
262
+	protected $_cap_restrictions = [
263
+		self::caps_read       => [],
264
+		self::caps_read_admin => [],
265
+		self::caps_edit       => [],
266
+		self::caps_delete     => [],
267
+	];
268
+
269
+	/**
270
+	 * Array defining which cap restriction generators to use to create default
271
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
272
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
273
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
274
+	 * automatically set this to false (not just null).
275
+	 *
276
+	 * @var EE_Restriction_Generator_Base[]
277
+	 */
278
+	protected $_cap_restriction_generators = [];
279
+
280
+	/**
281
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
282
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
283
+	 * maps to 'read' because when looking for relevant permissions we're going to use
284
+	 * 'read' in teh capabilities names like 'ee_read_events' etc.
285
+	 *
286
+	 * @var array
287
+	 */
288
+	protected $_cap_contexts_to_cap_action_map = [
289
+		self::caps_read       => 'read',
290
+		self::caps_read_admin => 'read',
291
+		self::caps_edit       => 'edit',
292
+		self::caps_delete     => 'delete',
293
+	];
294
+
295
+	/**
296
+	 * Timezone
297
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
298
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
299
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
300
+	 * EE_Datetime_Field data type will have access to it.
301
+	 *
302
+	 * @var string
303
+	 */
304
+	protected $_timezone;
305
+
306
+
307
+	/**
308
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
309
+	 * multisite.
310
+	 *
311
+	 * @var int
312
+	 */
313
+	protected static $_model_query_blog_id;
314
+
315
+	/**
316
+	 * A copy of _fields, except the array keys are the model names pointed to by
317
+	 * the field
318
+	 *
319
+	 * @var EE_Model_Field_Base[]
320
+	 */
321
+	private $_cache_foreign_key_to_fields = [];
322
+
323
+	/**
324
+	 * Cached list of all the fields on the model, indexed by their name
325
+	 *
326
+	 * @var EE_Model_Field_Base[]
327
+	 */
328
+	private $_cached_fields = null;
329
+
330
+	/**
331
+	 * Cached list of all the fields on the model, except those that are
332
+	 * marked as only pertinent to the database
333
+	 *
334
+	 * @var EE_Model_Field_Base[]
335
+	 */
336
+	private $_cached_fields_non_db_only = null;
337
+
338
+	/**
339
+	 * A cached reference to the primary key for quick lookup
340
+	 *
341
+	 * @var EE_Model_Field_Base
342
+	 */
343
+	private $_primary_key_field = null;
344
+
345
+	/**
346
+	 * Flag indicating whether this model has a primary key or not
347
+	 *
348
+	 * @var boolean
349
+	 */
350
+	protected $_has_primary_key_field = null;
351
+
352
+	/**
353
+	 * array in the format:  [ FK alias => full PK ]
354
+	 * where keys are local column name aliases for foreign keys
355
+	 * and values are the fully qualified column name for the primary key they represent
356
+	 *  ex:
357
+	 *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
358
+	 *
359
+	 * @var array $foreign_key_aliases
360
+	 */
361
+	protected $foreign_key_aliases = [];
362
+
363
+	/**
364
+	 * Whether or not this model is based off a table in WP core only (CPTs should set
365
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
366
+	 * This should be true for models that deal with data that should exist independent of EE.
367
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
368
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
369
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
370
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
371
+	 *
372
+	 * @var boolean
373
+	 */
374
+	protected $_wp_core_model = false;
375
+
376
+	/**
377
+	 * @var bool stores whether this model has a password field or not.
378
+	 * null until initialized by hasPasswordField()
379
+	 */
380
+	protected $has_password_field;
381
+
382
+	/**
383
+	 * @var EE_Password_Field|null Automatically set when calling getPasswordField()
384
+	 */
385
+	protected $password_field;
386
+
387
+	/**
388
+	 *    List of valid operators that can be used for querying.
389
+	 * The keys are all operators we'll accept, the values are the real SQL
390
+	 * operators used
391
+	 *
392
+	 * @var array
393
+	 */
394
+	protected $_valid_operators = [
395
+		'='           => '=',
396
+		'<='          => '<=',
397
+		'<'           => '<',
398
+		'>='          => '>=',
399
+		'>'           => '>',
400
+		'!='          => '!=',
401
+		'LIKE'        => 'LIKE',
402
+		'like'        => 'LIKE',
403
+		'NOT_LIKE'    => 'NOT LIKE',
404
+		'not_like'    => 'NOT LIKE',
405
+		'NOT LIKE'    => 'NOT LIKE',
406
+		'not like'    => 'NOT LIKE',
407
+		'IN'          => 'IN',
408
+		'in'          => 'IN',
409
+		'NOT_IN'      => 'NOT IN',
410
+		'not_in'      => 'NOT IN',
411
+		'NOT IN'      => 'NOT IN',
412
+		'not in'      => 'NOT IN',
413
+		'between'     => 'BETWEEN',
414
+		'BETWEEN'     => 'BETWEEN',
415
+		'IS_NOT_NULL' => 'IS NOT NULL',
416
+		'is_not_null' => 'IS NOT NULL',
417
+		'IS NOT NULL' => 'IS NOT NULL',
418
+		'is not null' => 'IS NOT NULL',
419
+		'IS_NULL'     => 'IS NULL',
420
+		'is_null'     => 'IS NULL',
421
+		'IS NULL'     => 'IS NULL',
422
+		'is null'     => 'IS NULL',
423
+		'REGEXP'      => 'REGEXP',
424
+		'regexp'      => 'REGEXP',
425
+		'NOT_REGEXP'  => 'NOT REGEXP',
426
+		'not_regexp'  => 'NOT REGEXP',
427
+		'NOT REGEXP'  => 'NOT REGEXP',
428
+		'not regexp'  => 'NOT REGEXP',
429
+	];
430
+
431
+	/**
432
+	 * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
433
+	 *
434
+	 * @var array
435
+	 */
436
+	protected $_in_style_operators = ['IN', 'NOT IN'];
437
+
438
+	/**
439
+	 * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
440
+	 * '12-31-2012'"
441
+	 *
442
+	 * @var array
443
+	 */
444
+	protected $_between_style_operators = ['BETWEEN'];
445
+
446
+	/**
447
+	 * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
448
+	 *
449
+	 * @var array
450
+	 */
451
+	protected $_like_style_operators = ['LIKE', 'NOT LIKE'];
452
+
453
+	/**
454
+	 * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
455
+	 * on a join table.
456
+	 *
457
+	 * @var array
458
+	 */
459
+	protected $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
460
+
461
+	/**
462
+	 * Allowed values for $query_params['order'] for ordering in queries
463
+	 *
464
+	 * @var array
465
+	 */
466
+	protected $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
467
+
468
+	/**
469
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
470
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
471
+	 *
472
+	 * @var array
473
+	 */
474
+	private $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
475
+
476
+	/**
477
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
478
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
479
+	 *
480
+	 * @var array
481
+	 */
482
+	private $_allowed_query_params = [
483
+		0,
484
+		'limit',
485
+		'order_by',
486
+		'group_by',
487
+		'having',
488
+		'force_join',
489
+		'order',
490
+		'on_join_limit',
491
+		'default_where_conditions',
492
+		'caps',
493
+		'extra_selects',
494
+		'exclude_protected',
495
+	];
496
+
497
+	/**
498
+	 * All the data types that can be used in $wpdb->prepare statements.
499
+	 *
500
+	 * @var array
501
+	 */
502
+	private $_valid_wpdb_data_types = ['%d', '%s', '%f'];
503
+
504
+	/**
505
+	 * @var EE_Registry $EE
506
+	 */
507
+	protected $EE = null;
508
+
509
+
510
+	/**
511
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
512
+	 *
513
+	 * @var int
514
+	 */
515
+	protected $_show_next_x_db_queries = 0;
516
+
517
+	/**
518
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
519
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
520
+	 * WHERE, GROUP_BY, etc.
521
+	 *
522
+	 * @var CustomSelects
523
+	 */
524
+	protected $_custom_selections = [];
525
+
526
+	/**
527
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
528
+	 * caches every model object we've fetched from the DB on this request
529
+	 *
530
+	 * @var array
531
+	 */
532
+	protected $_entity_map;
533
+
534
+	/**
535
+	 * @var LoaderInterface $loader
536
+	 */
537
+	private static $loader;
538
+
539
+	/**
540
+	 * indicates whether an EEM_Base child has already re-verified the DB
541
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
542
+	 * looking like EEM_Base::db_verified_*
543
+	 *
544
+	 * @var int - 0 = none, 1 = core, 2 = addons
545
+	 */
546
+	protected static $_db_verification_level = EEM_Base::db_verified_none;
547
+
548
+
549
+	/**
550
+	 * About all child constructors:
551
+	 * they should define the _tables, _fields and _model_relations arrays.
552
+	 * Should ALWAYS be called after child constructor.
553
+	 * In order to make the child constructors to be as simple as possible, this parent constructor
554
+	 * finalizes constructing all the object's attributes.
555
+	 * Generally, rather than requiring a child to code
556
+	 * $this->_tables = array(
557
+	 *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
558
+	 *        ...);
559
+	 *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
560
+	 * each EE_Table has a function to set the table's alias after the constructor, using
561
+	 * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
562
+	 * do something similar.
563
+	 *
564
+	 * @param null $timezone
565
+	 * @throws EE_Error
566
+	 */
567
+	protected function __construct($timezone = null)
568
+	{
569
+		// check that the model has not been loaded too soon
570
+		if (! did_action('AHEE__EE_System__load_espresso_addons')) {
571
+			throw new EE_Error(
572
+				sprintf(
573
+					esc_html__(
574
+						'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
575
+						'event_espresso'
576
+					),
577
+					get_class($this)
578
+				)
579
+			);
580
+		}
581
+		/**
582
+		 * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
583
+		 */
584
+		if (empty(EEM_Base::$_model_query_blog_id)) {
585
+			EEM_Base::set_model_query_blog_id();
586
+		}
587
+		/**
588
+		 * Filters the list of tables on a model. It is best to NOT use this directly and instead
589
+		 * just use EE_Register_Model_Extension
590
+		 *
591
+		 * @var EE_Table_Base[] $_tables
592
+		 */
593
+		$this->_tables = (array)apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
594
+		foreach ($this->_tables as $table_alias => $table_obj) {
595
+			/** @var $table_obj EE_Table_Base */
596
+			$table_obj->_construct_finalize_with_alias($table_alias);
597
+			if ($table_obj instanceof EE_Secondary_Table) {
598
+				/** @var $table_obj EE_Secondary_Table */
599
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
600
+			}
601
+		}
602
+		/**
603
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
604
+		 * EE_Register_Model_Extension
605
+		 *
606
+		 * @param EE_Model_Field_Base[] $_fields
607
+		 */
608
+		$this->_fields = (array)apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
609
+		$this->_invalidate_field_caches();
610
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
611
+			if (! array_key_exists($table_alias, $this->_tables)) {
612
+				throw new EE_Error(
613
+					sprintf(
614
+						esc_html__(
615
+							"Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
616
+							'event_espresso'
617
+						),
618
+						$table_alias,
619
+						implode(",", $this->_fields)
620
+					)
621
+				);
622
+			}
623
+			foreach ($fields_for_table as $field_name => $field_obj) {
624
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
625
+				// primary key field base has a slightly different _construct_finalize
626
+				/** @var $field_obj EE_Model_Field_Base */
627
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
628
+			}
629
+		}
630
+		// everything is related to Extra_Meta
631
+		if (get_class($this) !== 'EEM_Extra_Meta') {
632
+			// make extra meta related to everything, but don't block deleting things just
633
+			// because they have related extra meta info. For now just orphan those extra meta
634
+			// in the future we should automatically delete them
635
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
636
+		}
637
+		// and change logs
638
+		if (get_class($this) !== 'EEM_Change_Log') {
639
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
640
+		}
641
+		/**
642
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
643
+		 * EE_Register_Model_Extension
644
+		 *
645
+		 * @param EE_Model_Relation_Base[] $_model_relations
646
+		 */
647
+		$this->_model_relations = (array)apply_filters(
648
+			'FHEE__' . get_class($this) . '__construct__model_relations',
649
+			$this->_model_relations
650
+		);
651
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
652
+			/** @var $relation_obj EE_Model_Relation_Base */
653
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
654
+		}
655
+		foreach ($this->_indexes as $index_name => $index_obj) {
656
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
657
+		}
658
+		$this->set_timezone($timezone);
659
+		// finalize default where condition strategy, or set default
660
+		if (! $this->_default_where_conditions_strategy) {
661
+			// nothing was set during child constructor, so set default
662
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
663
+		}
664
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
665
+		if (! $this->_minimum_where_conditions_strategy) {
666
+			// nothing was set during child constructor, so set default
667
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
668
+		}
669
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
670
+		// if the cap slug hasn't been set, and we haven't set it to false on purpose
671
+		// to indicate to NOT set it, set it to the logical default
672
+		if ($this->_caps_slug === null) {
673
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
674
+		}
675
+		// initialize the standard cap restriction generators if none were specified by the child constructor
676
+		if ($this->_cap_restriction_generators !== false) {
677
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
678
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
679
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
680
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
681
+						new EE_Restriction_Generator_Protected(),
682
+						$cap_context,
683
+						$this
684
+					);
685
+				}
686
+			}
687
+		}
688
+		// if there are cap restriction generators, use them to make the default cap restrictions
689
+		if ($this->_cap_restriction_generators !== false) {
690
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
691
+				if (! $generator_object) {
692
+					continue;
693
+				}
694
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
695
+					throw new EE_Error(
696
+						sprintf(
697
+							esc_html__(
698
+								'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
699
+								'event_espresso'
700
+							),
701
+							$context,
702
+							$this->get_this_model_name()
703
+						)
704
+					);
705
+				}
706
+				$action = $this->cap_action_for_context($context);
707
+				if (! $generator_object->construction_finalized()) {
708
+					$generator_object->_construct_finalize($this, $action);
709
+				}
710
+			}
711
+		}
712
+		do_action('AHEE__' . get_class($this) . '__construct__end');
713
+	}
714
+
715
+
716
+	/**
717
+	 * Used to set the $_model_query_blog_id static property.
718
+	 *
719
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
720
+	 *                      value for get_current_blog_id() will be used.
721
+	 */
722
+	public static function set_model_query_blog_id($blog_id = 0)
723
+	{
724
+		EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int)$blog_id : get_current_blog_id();
725
+	}
726
+
727
+
728
+	/**
729
+	 * Returns whatever is set as the internal $model_query_blog_id.
730
+	 *
731
+	 * @return int
732
+	 */
733
+	public static function get_model_query_blog_id()
734
+	{
735
+		return EEM_Base::$_model_query_blog_id;
736
+	}
737
+
738
+
739
+	/**
740
+	 * This function is a singleton method used to instantiate the Espresso_model object
741
+	 *
742
+	 * @param string $timezone        string representing the timezone we want to set for returned Date Time Strings
743
+	 *                                (and any incoming timezone data that gets saved).
744
+	 *                                Note this just sends the timezone info to the date time model field objects.
745
+	 *                                Default is NULL
746
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
747
+	 * @return static (as in the concrete child class)
748
+	 * @throws EE_Error
749
+	 * @throws InvalidArgumentException
750
+	 * @throws InvalidDataTypeException
751
+	 * @throws InvalidInterfaceException
752
+	 */
753
+	public static function instance($timezone = null)
754
+	{
755
+		// check if instance of Espresso_model already exists
756
+		if (! static::$_instance instanceof static) {
757
+			// instantiate Espresso_model
758
+			static::$_instance = new static(
759
+				$timezone,
760
+				LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
761
+			);
762
+		}
763
+		// we might have a timezone set, let set_timezone decide what to do with it
764
+		static::$_instance->set_timezone($timezone);
765
+		// Espresso_model object
766
+		return static::$_instance;
767
+	}
768
+
769
+
770
+	/**
771
+	 * resets the model and returns it
772
+	 *
773
+	 * @param null | string $timezone
774
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
775
+	 * all its properties reset; if it wasn't instantiated, returns null)
776
+	 * @throws EE_Error
777
+	 * @throws ReflectionException
778
+	 * @throws InvalidArgumentException
779
+	 * @throws InvalidDataTypeException
780
+	 * @throws InvalidInterfaceException
781
+	 */
782
+	public static function reset($timezone = null)
783
+	{
784
+		if (static::$_instance instanceof EEM_Base) {
785
+			// let's try to NOT swap out the current instance for a new one
786
+			// because if someone has a reference to it, we can't remove their reference
787
+			// so it's best to keep using the same reference, but change the original object
788
+			// reset all its properties to their original values as defined in the class
789
+			$r                 = new ReflectionClass(get_class(static::$_instance));
790
+			$static_properties = $r->getStaticProperties();
791
+			foreach ($r->getDefaultProperties() as $property => $value) {
792
+				// don't set instance to null like it was originally,
793
+				// but it's static anyways, and we're ignoring static properties (for now at least)
794
+				if (! isset($static_properties[ $property ])) {
795
+					static::$_instance->{$property} = $value;
796
+				}
797
+			}
798
+			// and then directly call its constructor again, like we would if we were creating a new one
799
+			static::$_instance->__construct(
800
+				$timezone,
801
+				LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
802
+			);
803
+			return self::instance();
804
+		}
805
+		return null;
806
+	}
807
+
808
+
809
+	/**
810
+	 * @return LoaderInterface
811
+	 * @throws InvalidArgumentException
812
+	 * @throws InvalidDataTypeException
813
+	 * @throws InvalidInterfaceException
814
+	 */
815
+	private static function getLoader()
816
+	{
817
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
818
+			EEM_Base::$loader = LoaderFactory::getLoader();
819
+		}
820
+		return EEM_Base::$loader;
821
+	}
822
+
823
+
824
+	/**
825
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
826
+	 *
827
+	 * @param boolean $translated return localized strings or JUST the array.
828
+	 * @return array
829
+	 * @throws EE_Error
830
+	 * @throws InvalidArgumentException
831
+	 * @throws InvalidDataTypeException
832
+	 * @throws InvalidInterfaceException
833
+	 * @throws ReflectionException
834
+	 */
835
+	public function status_array($translated = false)
836
+	{
837
+		if (! array_key_exists('Status', $this->_model_relations)) {
838
+			return [];
839
+		}
840
+		$model_name   = $this->get_this_model_name();
841
+		$status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
842
+		$stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
843
+		$status_array = [];
844
+		foreach ($stati as $status) {
845
+			$status_array[ $status->ID() ] = $status->get('STS_code');
846
+		}
847
+		return $translated
848
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
849
+			: $status_array;
850
+	}
851
+
852
+
853
+	/**
854
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
855
+	 *
856
+	 * @param array $query_params             see github link below for more info
857
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
858
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
859
+	 *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
860
+	 *                                        Some full examples: get 10 transactions which have Scottish attendees:
861
+	 *                                        EEM_Transaction::instance()->get_all( array( array(
862
+	 *                                        'OR'=>array(
863
+	 *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
864
+	 *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
865
+	 *                                        )
866
+	 *                                        ),
867
+	 *                                        'limit'=>10,
868
+	 *                                        'group_by'=>'TXN_ID'
869
+	 *                                        ));
870
+	 *                                        get all the answers to the question titled "shirt size" for event with id
871
+	 *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
872
+	 *                                        'Question.QST_display_text'=>'shirt size',
873
+	 *                                        'Registration.Event.EVT_ID'=>12
874
+	 *                                        ),
875
+	 *                                        'order_by'=>array('ANS_value'=>'ASC')
876
+	 *                                        ));
877
+	 * @throws EE_Error
878
+	 * @throws ReflectionException
879
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
880
+	 *                                        or if you have the development copy of EE you can view this at the path:
881
+	 *                                        /docs/G--Model-System/model-query-params.md
882
+	 */
883
+	public function get_all($query_params = [])
884
+	{
885
+		if (
886
+			isset($query_params['limit'])
887
+			&& ! isset($query_params['group_by'])
888
+		) {
889
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
890
+		}
891
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
892
+	}
893
+
894
+
895
+	/**
896
+	 * Modifies the query parameters so we only get back model objects
897
+	 * that "belong" to the current user
898
+	 *
899
+	 * @param array $query_params see github link below for more info
900
+	 * @return array
901
+	 * @throws ReflectionException
902
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
903
+	 */
904
+	public function alter_query_params_to_only_include_mine($query_params = [])
905
+	{
906
+		$wp_user_field_name = $this->wp_user_field_name();
907
+		if ($wp_user_field_name) {
908
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
909
+		}
910
+		return $query_params;
911
+	}
912
+
913
+
914
+	/**
915
+	 * Returns the name of the field's name that points to the WP_User table
916
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
917
+	 * foreign key to the WP_User table)
918
+	 *
919
+	 * @return string|boolean string on success, boolean false when there is no
920
+	 * foreign key to the WP_User table
921
+	 * @throws ReflectionException
922
+	 */
923
+	public function wp_user_field_name()
924
+	{
925
+		try {
926
+			if (! empty($this->_model_chain_to_wp_user)) {
927
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
928
+				$last_model_name              = end($models_to_follow_to_wp_users);
929
+				$model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
930
+				$model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
931
+			} else {
932
+				$model_with_fk_to_wp_users = $this;
933
+				$model_chain_to_wp_user    = '';
934
+			}
935
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
936
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
937
+		} catch (EE_Error $e) {
938
+			return false;
939
+		}
940
+	}
941
+
942
+
943
+	/**
944
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
945
+	 * (or transiently-related model) has a foreign key to the wp_users table;
946
+	 * useful for finding if model objects of this type are 'owned' by the current user.
947
+	 * This is an empty string when the foreign key is on this model and when it isn't,
948
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
949
+	 * (or transiently-related model)
950
+	 *
951
+	 * @return string
952
+	 */
953
+	public function model_chain_to_wp_user()
954
+	{
955
+		return $this->_model_chain_to_wp_user;
956
+	}
957
+
958
+
959
+	/**
960
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
961
+	 * like how registrations don't have a foreign key to wp_users, but the
962
+	 * events they are for are), or is unrelated to wp users.
963
+	 * generally available
964
+	 *
965
+	 * @return boolean
966
+	 */
967
+	public function is_owned()
968
+	{
969
+		if ($this->model_chain_to_wp_user()) {
970
+			return true;
971
+		}
972
+		try {
973
+			$this->get_foreign_key_to('WP_User');
974
+			return true;
975
+		} catch (EE_Error $e) {
976
+			return false;
977
+		}
978
+	}
979
+
980
+
981
+	/**
982
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
983
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
984
+	 * the model)
985
+	 *
986
+	 * @param array  $query_params
987
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
988
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
989
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
990
+	 *                                  override this and set the select to "*", or a specific column name, like
991
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
992
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
993
+	 *                                  the aliases used to refer to this selection, and values are to be
994
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
995
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
996
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
997
+	 * @throws EE_Error
998
+	 * @throws InvalidArgumentException
999
+	 * @throws ReflectionException
1000
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1001
+	 */
1002
+	protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1003
+	{
1004
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1005
+		$model_query_info         = $this->_create_model_query_info_carrier($query_params);
1006
+		$select_expressions       = $columns_to_select === null
1007
+			? $this->_construct_default_select_sql($model_query_info)
1008
+			: '';
1009
+		if ($this->_custom_selections instanceof CustomSelects) {
1010
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1011
+			$select_expressions .= $select_expressions
1012
+				? ', ' . $custom_expressions
1013
+				: $custom_expressions;
1014
+		}
1015
+
1016
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1017
+		return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1018
+	}
1019
+
1020
+
1021
+	/**
1022
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1023
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1024
+	 * method of including extra select information.
1025
+	 *
1026
+	 * @param array             $query_params
1027
+	 * @param null|array|string $columns_to_select
1028
+	 * @return null|CustomSelects
1029
+	 * @throws InvalidArgumentException
1030
+	 */
1031
+	protected function getCustomSelection(array $query_params, $columns_to_select = null)
1032
+	{
1033
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1034
+			return null;
1035
+		}
1036
+		$selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1037
+		$selects = is_string($selects) ? explode(',', $selects) : $selects;
1038
+		return new CustomSelects($selects);
1039
+	}
1040
+
1041
+
1042
+	/**
1043
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1044
+	 * but you can use the model query params to more easily
1045
+	 * take care of joins, field preparation etc.
1046
+	 *
1047
+	 * @param array  $query_params
1048
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1049
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1050
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1051
+	 *                                  override this and set the select to "*", or a specific column name, like
1052
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1053
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1054
+	 *                                  the aliases used to refer to this selection, and values are to be
1055
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1056
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1057
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1058
+	 * @throws EE_Error
1059
+	 * @throws ReflectionException
1060
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1061
+	 */
1062
+	public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1063
+	{
1064
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1065
+	}
1066
+
1067
+
1068
+	/**
1069
+	 * For creating a custom select statement
1070
+	 *
1071
+	 * @param array|string $columns_to_select either a string to be inserted directly as the select statement,
1072
+	 *                                        or an array where keys are aliases, and values are arrays where 0=>the
1073
+	 *                                        selection SQL, and 1=>is the datatype
1074
+	 * @return string
1075
+	 * @throws EE_Error
1076
+	 */
1077
+	private function _construct_select_from_input($columns_to_select)
1078
+	{
1079
+		if (is_array($columns_to_select)) {
1080
+			$select_sql_array = [];
1081
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1082
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1083
+					throw new EE_Error(
1084
+						sprintf(
1085
+							esc_html__(
1086
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1087
+								'event_espresso'
1088
+							),
1089
+							$selection_and_datatype,
1090
+							$alias
1091
+						)
1092
+					);
1093
+				}
1094
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1095
+					throw new EE_Error(
1096
+						sprintf(
1097
+							esc_html__(
1098
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1099
+								'event_espresso'
1100
+							),
1101
+							$selection_and_datatype[1],
1102
+							$selection_and_datatype[0],
1103
+							$alias,
1104
+							implode(', ', $this->_valid_wpdb_data_types)
1105
+						)
1106
+					);
1107
+				}
1108
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1109
+			}
1110
+			$columns_to_select_string = implode(', ', $select_sql_array);
1111
+		} else {
1112
+			$columns_to_select_string = $columns_to_select;
1113
+		}
1114
+		return $columns_to_select_string;
1115
+	}
1116
+
1117
+
1118
+	/**
1119
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1120
+	 *
1121
+	 * @return string
1122
+	 * @throws EE_Error
1123
+	 */
1124
+	public function primary_key_name()
1125
+	{
1126
+		return $this->get_primary_key_field()->get_name();
1127
+	}
1128
+
1129
+
1130
+	/**
1131
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1132
+	 * If there is no primary key on this model, $id is treated as primary key string
1133
+	 *
1134
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1135
+	 * @return EE_Base_Class
1136
+	 * @throws EE_Error
1137
+	 * @throws ReflectionException
1138
+	 */
1139
+	public function get_one_by_ID($id)
1140
+	{
1141
+		if ($this->get_from_entity_map($id)) {
1142
+			return $this->get_from_entity_map($id);
1143
+		}
1144
+		return $this->get_one(
1145
+			$this->alter_query_params_to_restrict_by_ID(
1146
+				$id,
1147
+				['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1148
+			)
1149
+		);
1150
+	}
1151
+
1152
+
1153
+	/**
1154
+	 * Alters query parameters to only get items with this ID are returned.
1155
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1156
+	 * or could just be a simple primary key ID
1157
+	 *
1158
+	 * @param int   $id
1159
+	 * @param array $query_params see github link below for more info
1160
+	 * @return array of normal query params,
1161
+	 * @throws EE_Error
1162
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1163
+	 */
1164
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1165
+	{
1166
+		if (! isset($query_params[0])) {
1167
+			$query_params[0] = [];
1168
+		}
1169
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1170
+		if ($conditions_from_id === null) {
1171
+			$query_params[0][ $this->primary_key_name() ] = $id;
1172
+		} else {
1173
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1174
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1175
+		}
1176
+		return $query_params;
1177
+	}
1178
+
1179
+
1180
+	/**
1181
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1182
+	 * array. If no item is found, null is returned.
1183
+	 *
1184
+	 * @param array $query_params like EEM_Base's $query_params variable.
1185
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1186
+	 * @throws EE_Error
1187
+	 * @throws ReflectionException
1188
+	 */
1189
+	public function get_one($query_params = [])
1190
+	{
1191
+		if (! is_array($query_params)) {
1192
+			EE_Error::doing_it_wrong(
1193
+				'EEM_Base::get_one',
1194
+				sprintf(
1195
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1196
+					gettype($query_params)
1197
+				),
1198
+				'4.6.0'
1199
+			);
1200
+			$query_params = [];
1201
+		}
1202
+		$query_params['limit'] = 1;
1203
+		$items                 = $this->get_all($query_params);
1204
+		if (empty($items)) {
1205
+			return null;
1206
+		}
1207
+		return array_shift($items);
1208
+	}
1209
+
1210
+
1211
+	/**
1212
+	 * Returns the next x number of items in sequence from the given value as
1213
+	 * found in the database matching the given query conditions.
1214
+	 *
1215
+	 * @param mixed $current_field_value    Value used for the reference point.
1216
+	 * @param null  $field_to_order_by      What field is used for the
1217
+	 *                                      reference point.
1218
+	 * @param int   $limit                  How many to return.
1219
+	 * @param array $query_params           Extra conditions on the query.
1220
+	 * @param null  $columns_to_select      If left null, then an array of
1221
+	 *                                      EE_Base_Class objects is returned,
1222
+	 *                                      otherwise you can indicate just the
1223
+	 *                                      columns you want returned.
1224
+	 * @return EE_Base_Class[]|array
1225
+	 * @throws EE_Error
1226
+	 * @throws ReflectionException
1227
+	 */
1228
+	public function next_x(
1229
+		$current_field_value,
1230
+		$field_to_order_by = null,
1231
+		$limit = 1,
1232
+		$query_params = [],
1233
+		$columns_to_select = null
1234
+	) {
1235
+		return $this->_get_consecutive(
1236
+			$current_field_value,
1237
+			'>',
1238
+			$field_to_order_by,
1239
+			$limit,
1240
+			$query_params,
1241
+			$columns_to_select
1242
+		);
1243
+	}
1244
+
1245
+
1246
+	/**
1247
+	 * Returns the previous x number of items in sequence from the given value
1248
+	 * as found in the database matching the given query conditions.
1249
+	 *
1250
+	 * @param mixed $current_field_value    Value used for the reference point.
1251
+	 * @param null  $field_to_order_by      What field is used for the
1252
+	 *                                      reference point.
1253
+	 * @param int   $limit                  How many to return.
1254
+	 * @param array $query_params           Extra conditions on the query.
1255
+	 * @param null  $columns_to_select      If left null, then an array of
1256
+	 *                                      EE_Base_Class objects is returned,
1257
+	 *                                      otherwise you can indicate just the
1258
+	 *                                      columns you want returned.
1259
+	 * @return EE_Base_Class[]|array
1260
+	 * @throws EE_Error
1261
+	 * @throws ReflectionException
1262
+	 */
1263
+	public function previous_x(
1264
+		$current_field_value,
1265
+		$field_to_order_by = null,
1266
+		$limit = 1,
1267
+		$query_params = [],
1268
+		$columns_to_select = null
1269
+	) {
1270
+		return $this->_get_consecutive(
1271
+			$current_field_value,
1272
+			'<',
1273
+			$field_to_order_by,
1274
+			$limit,
1275
+			$query_params,
1276
+			$columns_to_select
1277
+		);
1278
+	}
1279
+
1280
+
1281
+	/**
1282
+	 * Returns the next item in sequence from the given value as found in the
1283
+	 * database matching the given query conditions.
1284
+	 *
1285
+	 * @param mixed $current_field_value    Value used for the reference point.
1286
+	 * @param null  $field_to_order_by      What field is used for the
1287
+	 *                                      reference point.
1288
+	 * @param array $query_params           Extra conditions on the query.
1289
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1290
+	 *                                      object is returned, otherwise you
1291
+	 *                                      can indicate just the columns you
1292
+	 *                                      want and a single array indexed by
1293
+	 *                                      the columns will be returned.
1294
+	 * @return EE_Base_Class|null|array()
1295
+	 * @throws EE_Error
1296
+	 * @throws ReflectionException
1297
+	 */
1298
+	public function next(
1299
+		$current_field_value,
1300
+		$field_to_order_by = null,
1301
+		$query_params = [],
1302
+		$columns_to_select = null
1303
+	) {
1304
+		$results = $this->_get_consecutive(
1305
+			$current_field_value,
1306
+			'>',
1307
+			$field_to_order_by,
1308
+			1,
1309
+			$query_params,
1310
+			$columns_to_select
1311
+		);
1312
+		return empty($results) ? null : reset($results);
1313
+	}
1314
+
1315
+
1316
+	/**
1317
+	 * Returns the previous item in sequence from the given value as found in
1318
+	 * the database matching the given query conditions.
1319
+	 *
1320
+	 * @param mixed $current_field_value    Value used for the reference point.
1321
+	 * @param null  $field_to_order_by      What field is used for the
1322
+	 *                                      reference point.
1323
+	 * @param array $query_params           Extra conditions on the query.
1324
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1325
+	 *                                      object is returned, otherwise you
1326
+	 *                                      can indicate just the columns you
1327
+	 *                                      want and a single array indexed by
1328
+	 *                                      the columns will be returned.
1329
+	 * @return EE_Base_Class|null|array()
1330
+	 * @throws EE_Error
1331
+	 * @throws ReflectionException
1332
+	 */
1333
+	public function previous(
1334
+		$current_field_value,
1335
+		$field_to_order_by = null,
1336
+		$query_params = [],
1337
+		$columns_to_select = null
1338
+	) {
1339
+		$results = $this->_get_consecutive(
1340
+			$current_field_value,
1341
+			'<',
1342
+			$field_to_order_by,
1343
+			1,
1344
+			$query_params,
1345
+			$columns_to_select
1346
+		);
1347
+		return empty($results) ? null : reset($results);
1348
+	}
1349
+
1350
+
1351
+	/**
1352
+	 * Returns the a consecutive number of items in sequence from the given
1353
+	 * value as found in the database matching the given query conditions.
1354
+	 *
1355
+	 * @param mixed  $current_field_value   Value used for the reference point.
1356
+	 * @param string $operand               What operand is used for the sequence.
1357
+	 * @param string $field_to_order_by     What field is used for the reference point.
1358
+	 * @param int    $limit                 How many to return.
1359
+	 * @param array  $query_params          Extra conditions on the query.
1360
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1361
+	 *                                      otherwise you can indicate just the columns you want returned.
1362
+	 * @return EE_Base_Class[]|array
1363
+	 * @throws EE_Error
1364
+	 * @throws ReflectionException
1365
+	 */
1366
+	protected function _get_consecutive(
1367
+		$current_field_value,
1368
+		$operand = '>',
1369
+		$field_to_order_by = null,
1370
+		$limit = 1,
1371
+		$query_params = [],
1372
+		$columns_to_select = null
1373
+	) {
1374
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1375
+		if (empty($field_to_order_by)) {
1376
+			if ($this->has_primary_key_field()) {
1377
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1378
+			} else {
1379
+				if (WP_DEBUG) {
1380
+					throw new EE_Error(
1381
+						esc_html__(
1382
+							'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1383
+							'event_espresso'
1384
+						)
1385
+					);
1386
+				}
1387
+				EE_Error::add_error(__('There was an error with the query.', 'event_espresso'));
1388
+				return [];
1389
+			}
1390
+		}
1391
+		if (! is_array($query_params)) {
1392
+			EE_Error::doing_it_wrong(
1393
+				'EEM_Base::_get_consecutive',
1394
+				sprintf(
1395
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1396
+					gettype($query_params)
1397
+				),
1398
+				'4.6.0'
1399
+			);
1400
+			$query_params = [];
1401
+		}
1402
+		// let's add the where query param for consecutive look up.
1403
+		$query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1404
+		$query_params['limit']                 = $limit;
1405
+		// set direction
1406
+		$incoming_orderby         = isset($query_params['order_by']) ? (array)$query_params['order_by'] : [];
1407
+		$query_params['order_by'] = $operand === '>'
1408
+			? [$field_to_order_by => 'ASC'] + $incoming_orderby
1409
+			: [$field_to_order_by => 'DESC'] + $incoming_orderby;
1410
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1411
+		if (empty($columns_to_select)) {
1412
+			return $this->get_all($query_params);
1413
+		}
1414
+		// getting just the fields
1415
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1416
+	}
1417
+
1418
+
1419
+	/**
1420
+	 * This sets the _timezone property after model object has been instantiated.
1421
+	 *
1422
+	 * @param string|null $timezone valid PHP DateTimeZone timezone string
1423
+	 */
1424
+	public function set_timezone($timezone)
1425
+	{
1426
+		// don't set the timezone if the incoming value is the same
1427
+		if (! empty($timezone) && $timezone === $this->_timezone) {
1428
+			return;
1429
+		}
1430
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
1431
+		// note we need to loop through relations and set the timezone on those objects as well.
1432
+		foreach ($this->_model_relations as $relation) {
1433
+			$relation->set_timezone($this->_timezone);
1434
+		}
1435
+		// and finally we do the same for any datetime fields
1436
+		foreach ($this->_fields as $field) {
1437
+			if ($field instanceof EE_Datetime_Field) {
1438
+				$field->set_timezone($this->_timezone);
1439
+			}
1440
+		}
1441
+	}
1442
+
1443
+
1444
+	/**
1445
+	 * This just returns whatever is set for the current timezone.
1446
+	 *
1447
+	 * @access public
1448
+	 * @return string
1449
+	 */
1450
+	public function get_timezone()
1451
+	{
1452
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1453
+		if (empty($this->_timezone)) {
1454
+			foreach ($this->_fields as $field) {
1455
+				if ($field instanceof EE_Datetime_Field) {
1456
+					$this->set_timezone($field->get_timezone());
1457
+					break;
1458
+				}
1459
+			}
1460
+		}
1461
+		// if timezone STILL empty then return the default timezone for the site.
1462
+		if (empty($this->_timezone)) {
1463
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1464
+		}
1465
+		return $this->_timezone;
1466
+	}
1467
+
1468
+
1469
+	/**
1470
+	 * This returns the date formats set for the given field name and also ensures that
1471
+	 * $this->_timezone property is set correctly.
1472
+	 *
1473
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1474
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1475
+	 * @return array formats in an array with the date format first, and the time format last.
1476
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1477
+	 * @since 4.6.x
1478
+	 */
1479
+	public function get_formats_for($field_name, $pretty = false)
1480
+	{
1481
+		$field_settings = $this->field_settings_for($field_name);
1482
+		// if not a valid EE_Datetime_Field then throw error
1483
+		if (! $field_settings instanceof EE_Datetime_Field) {
1484
+			throw new EE_Error(
1485
+				sprintf(
1486
+					esc_html__(
1487
+						'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1488
+						'event_espresso'
1489
+					),
1490
+					$field_name
1491
+				)
1492
+			);
1493
+		}
1494
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1495
+		// the field.
1496
+		$this->_timezone = $field_settings->get_timezone();
1497
+		return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1498
+	}
1499
+
1500
+
1501
+	/**
1502
+	 * This returns the current time in a format setup for a query on this model.
1503
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1504
+	 * it will return:
1505
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1506
+	 *  NOW
1507
+	 *  - or a unix timestamp (equivalent to time())
1508
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1509
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1510
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1511
+	 *
1512
+	 * @param string $field_name       The field the current time is needed for.
1513
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1514
+	 *                                 formatted string matching the set format for the field in the set timezone will
1515
+	 *                                 be returned.
1516
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1517
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1518
+	 *                                 exception is triggered.
1519
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1520
+	 * @throws Exception
1521
+	 * @since 4.6.x
1522
+	 */
1523
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1524
+	{
1525
+		$formats  = $this->get_formats_for($field_name);
1526
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1527
+		if ($timestamp) {
1528
+			return $DateTime->format('U');
1529
+		}
1530
+		// not returning timestamp, so return formatted string in timezone.
1531
+		switch ($what) {
1532
+			case 'time':
1533
+				return $DateTime->format($formats[1]);
1534
+			case 'date':
1535
+				return $DateTime->format($formats[0]);
1536
+			default:
1537
+				return $DateTime->format(implode(' ', $formats));
1538
+		}
1539
+	}
1540
+
1541
+
1542
+	/**
1543
+	 * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1544
+	 * for the model are.  Returns a DateTime object.
1545
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1546
+	 * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1547
+	 * ignored.
1548
+	 *
1549
+	 * @param string $field_name      The field being setup.
1550
+	 * @param string $timestring      The date time string being used.
1551
+	 * @param string $incoming_format The format for the time string.
1552
+	 * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1553
+	 *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1554
+	 *                                format is
1555
+	 *                                'U', this is ignored.
1556
+	 * @return DateTime
1557
+	 * @throws EE_Error
1558
+	 */
1559
+	public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1560
+	{
1561
+		// just using this to ensure the timezone is set correctly internally
1562
+		$this->get_formats_for($field_name);
1563
+		// load EEH_DTT_Helper
1564
+		$set_timezone     = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1565
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1566
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1567
+		return DbSafeDateTime::createFromDateTime($incomingDateTime);
1568
+	}
1569
+
1570
+
1571
+	/**
1572
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1573
+	 *
1574
+	 * @return EE_Table_Base[]
1575
+	 */
1576
+	public function get_tables()
1577
+	{
1578
+		return $this->_tables;
1579
+	}
1580
+
1581
+
1582
+	/**
1583
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1584
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1585
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1586
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1587
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1588
+	 * model object with EVT_ID = 1
1589
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1590
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1591
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1592
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1593
+	 * are not specified)
1594
+	 *
1595
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1596
+	 *                                         columns!), values are strings, integers, floats, and maybe arrays if
1597
+	 *                                         they
1598
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1599
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1600
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1601
+	 *                                         ID=34, we'd use this method as follows:
1602
+	 *                                         EEM_Transaction::instance()->update(
1603
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1604
+	 *                                         array(array('TXN_ID'=>34)));
1605
+	 * @param array   $query_params            Eg, consider updating Question's QST_admin_label field is of type
1606
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1607
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1608
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1609
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1610
+	 *                                         TRUE, it is assumed that you've already called
1611
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1612
+	 *                                         malicious javascript. However, if
1613
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1614
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1615
+	 *                                         and every other field, before insertion. We provide this parameter
1616
+	 *                                         because model objects perform their prepare_for_set function on all
1617
+	 *                                         their values, and so don't need to be called again (and in many cases,
1618
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1619
+	 *                                         prepare_for_set method...)
1620
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1621
+	 *                                         in this model's entity map according to $fields_n_values that match
1622
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1623
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1624
+	 *                                         could get out-of-sync with the database
1625
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1626
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1627
+	 *                                         bad)
1628
+	 * @throws EE_Error
1629
+	 * @throws ReflectionException
1630
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1631
+	 */
1632
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1633
+	{
1634
+		if (! is_array($query_params)) {
1635
+			EE_Error::doing_it_wrong(
1636
+				'EEM_Base::update',
1637
+				sprintf(
1638
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1639
+					gettype($query_params)
1640
+				),
1641
+				'4.6.0'
1642
+			);
1643
+			$query_params = [];
1644
+		}
1645
+		/**
1646
+		 * Action called before a model update call has been made.
1647
+		 *
1648
+		 * @param EEM_Base $model
1649
+		 * @param array    $fields_n_values the updated fields and their new values
1650
+		 * @param array    $query_params
1651
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1652
+		 */
1653
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1654
+		/**
1655
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1656
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1657
+		 *
1658
+		 * @param array    $fields_n_values fields and their new values
1659
+		 * @param EEM_Base $model           the model being queried
1660
+		 * @param array    $query_params
1661
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1662
+		 */
1663
+		$fields_n_values = (array)apply_filters(
1664
+			'FHEE__EEM_Base__update__fields_n_values',
1665
+			$fields_n_values,
1666
+			$this,
1667
+			$query_params
1668
+		);
1669
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1670
+		// to do that, for each table, verify that it's PK isn't null.
1671
+		$tables = $this->get_tables();
1672
+		// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1673
+		// NOTE: we should make this code more efficient by NOT querying twice
1674
+		// before the real update, but that needs to first go through ALPHA testing
1675
+		// as it's dangerous. says Mike August 8 2014
1676
+		// we want to make sure the default_where strategy is ignored
1677
+		$this->_ignore_where_strategy = true;
1678
+		$wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1679
+		foreach ($wpdb_select_results as $wpdb_result) {
1680
+			// type cast stdClass as array
1681
+			$wpdb_result = (array)$wpdb_result;
1682
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1683
+			if ($this->has_primary_key_field()) {
1684
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1685
+			} else {
1686
+				// if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1687
+				$main_table_pk_value = null;
1688
+			}
1689
+			// if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1690
+			// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1691
+			if (count($tables) > 1) {
1692
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1693
+				// in that table, and so we'll want to insert one
1694
+				foreach ($tables as $table_obj) {
1695
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1696
+					// if there is no private key for this table on the results, it means there's no entry
1697
+					// in this table, right? so insert a row in the current table, using any fields available
1698
+					if (
1699
+					! (array_key_exists($this_table_pk_column, $wpdb_result)
1700
+					   && $wpdb_result[ $this_table_pk_column ])
1701
+					) {
1702
+						$success = $this->_insert_into_specific_table(
1703
+							$table_obj,
1704
+							$fields_n_values,
1705
+							$main_table_pk_value
1706
+						);
1707
+						// if we died here, report the error
1708
+						if (! $success) {
1709
+							return false;
1710
+						}
1711
+					}
1712
+				}
1713
+			}
1714
+			//              //and now check that if we have cached any models by that ID on the model, that
1715
+			//              //they also get updated properly
1716
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1717
+			//              if( $model_object ){
1718
+			//                  foreach( $fields_n_values as $field => $value ){
1719
+			//                      $model_object->set($field, $value);
1720
+			// let's make sure default_where strategy is followed now
1721
+			$this->_ignore_where_strategy = false;
1722
+		}
1723
+		// if we want to keep model objects in sync, AND
1724
+		// if this wasn't called from a model object (to update itself)
1725
+		// then we want to make sure we keep all the existing
1726
+		// model objects in sync with the db
1727
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1728
+			if ($this->has_primary_key_field()) {
1729
+				$model_objs_affected_ids = $this->get_col($query_params);
1730
+			} else {
1731
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1732
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params);
1733
+				$model_objs_affected_ids     = [];
1734
+				foreach ($models_affected_key_columns as $row) {
1735
+					$combined_index_key                             = $this->get_index_primary_key_string($row);
1736
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1737
+				}
1738
+			}
1739
+			if (! $model_objs_affected_ids) {
1740
+				// wait wait wait- if nothing was affected let's stop here
1741
+				return 0;
1742
+			}
1743
+			foreach ($model_objs_affected_ids as $id) {
1744
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1745
+				if ($model_obj_in_entity_map) {
1746
+					foreach ($fields_n_values as $field => $new_value) {
1747
+						$model_obj_in_entity_map->set($field, $new_value);
1748
+					}
1749
+				}
1750
+			}
1751
+			// if there is a primary key on this model, we can now do a slight optimization
1752
+			if ($this->has_primary_key_field()) {
1753
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1754
+				$query_params = [
1755
+					[$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1756
+					'limit'                    => count($model_objs_affected_ids),
1757
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1758
+				];
1759
+			}
1760
+		}
1761
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1762
+		$SQL              = "UPDATE "
1763
+							. $model_query_info->get_full_join_sql()
1764
+							. " SET "
1765
+							. $this->_construct_update_sql($fields_n_values)
1766
+							. $model_query_info->get_where_sql(
1767
+			);// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1768
+		$rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1769
+		/**
1770
+		 * Action called after a model update call has been made.
1771
+		 *
1772
+		 * @param EEM_Base $model
1773
+		 * @param array    $fields_n_values the updated fields and their new values
1774
+		 * @param array    $query_params
1775
+		 * @param int      $rows_affected
1776
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1777
+		 */
1778
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1779
+		return $rows_affected;// how many supposedly got updated
1780
+	}
1781
+
1782
+
1783
+	/**
1784
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1785
+	 * are teh values of the field specified (or by default the primary key field)
1786
+	 * that matched the query params. Note that you should pass the name of the
1787
+	 * model FIELD, not the database table's column name.
1788
+	 *
1789
+	 * @param array  $query_params
1790
+	 * @param string $field_to_select
1791
+	 * @return array just like $wpdb->get_col()
1792
+	 * @throws EE_Error
1793
+	 * @throws ReflectionException
1794
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1795
+	 */
1796
+	public function get_col($query_params = [], $field_to_select = null)
1797
+	{
1798
+		if ($field_to_select) {
1799
+			$field = $this->field_settings_for($field_to_select);
1800
+		} elseif ($this->has_primary_key_field()) {
1801
+			$field = $this->get_primary_key_field();
1802
+		} else {
1803
+			$field_settings = $this->field_settings();
1804
+			// no primary key, just grab the first column
1805
+			$field = reset($field_settings);
1806
+		}
1807
+		$model_query_info   = $this->_create_model_query_info_carrier($query_params);
1808
+		$select_expressions = $field->get_qualified_column();
1809
+		$SQL                =
1810
+			"SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1811
+		return $this->_do_wpdb_query('get_col', [$SQL]);
1812
+	}
1813
+
1814
+
1815
+	/**
1816
+	 * Returns a single column value for a single row from the database
1817
+	 *
1818
+	 * @param array  $query_params
1819
+	 * @param string $field_to_select
1820
+	 * @return string
1821
+	 * @throws EE_Error
1822
+	 * @throws ReflectionException
1823
+	 * @see EEM_Base::get_col()
1824
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1825
+	 */
1826
+	public function get_var($query_params = [], $field_to_select = null)
1827
+	{
1828
+		$query_params['limit'] = 1;
1829
+		$col                   = $this->get_col($query_params, $field_to_select);
1830
+		if (! empty($col)) {
1831
+			return reset($col);
1832
+		}
1833
+		return null;
1834
+	}
1835
+
1836
+
1837
+	/**
1838
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1839
+	 * time?', Question.desc='what do you think?'..." Values are filtered through wpdb->prepare to avoid against SQL
1840
+	 * injection, but currently no further filtering is done
1841
+	 *
1842
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1843
+	 *                               be updated to in the DB
1844
+	 * @return string of SQL
1845
+	 * @throws EE_Error
1846
+	 * @global      $wpdb
1847
+	 */
1848
+	public function _construct_update_sql($fields_n_values)
1849
+	{
1850
+		global $wpdb;
1851
+		$cols_n_values = [];
1852
+		foreach ($fields_n_values as $field_name => $value) {
1853
+			$field_obj = $this->field_settings_for($field_name);
1854
+			// if the value is NULL, we want to assign the value to that.
1855
+			// wpdb->prepare doesn't really handle that properly
1856
+			$prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1857
+			$value_sql       = $prepared_value === null ? 'NULL'
1858
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1859
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1860
+		}
1861
+		return implode(",", $cols_n_values);
1862
+	}
1863
+
1864
+
1865
+	/**
1866
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1867
+	 * Performs a HARD delete, meaning the database row should always be removed,
1868
+	 * not just have a flag field on it switched
1869
+	 * Wrapper for EEM_Base::delete_permanently()
1870
+	 *
1871
+	 * @param mixed   $id
1872
+	 * @param boolean $allow_blocking
1873
+	 * @return int the number of rows deleted
1874
+	 * @throws EE_Error
1875
+	 * @throws ReflectionException
1876
+	 */
1877
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1878
+	{
1879
+		return $this->delete_permanently(
1880
+			[
1881
+				[$this->get_primary_key_field()->get_name() => $id],
1882
+				'limit' => 1,
1883
+			],
1884
+			$allow_blocking
1885
+		);
1886
+	}
1887
+
1888
+
1889
+	/**
1890
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1891
+	 * Wrapper for EEM_Base::delete()
1892
+	 *
1893
+	 * @param mixed   $id
1894
+	 * @param boolean $allow_blocking
1895
+	 * @return int the number of rows deleted
1896
+	 * @throws EE_Error
1897
+	 * @throws ReflectionException
1898
+	 */
1899
+	public function delete_by_ID($id, $allow_blocking = true)
1900
+	{
1901
+		return $this->delete(
1902
+			[
1903
+				[$this->get_primary_key_field()->get_name() => $id],
1904
+				'limit' => 1,
1905
+			],
1906
+			$allow_blocking
1907
+		);
1908
+	}
1909
+
1910
+
1911
+	/**
1912
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1913
+	 * meaning if the model has a field that indicates its been "trashed" or
1914
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1915
+	 *
1916
+	 * @param array   $query_params
1917
+	 * @param boolean $allow_blocking
1918
+	 * @return int how many rows got deleted
1919
+	 * @throws EE_Error
1920
+	 * @throws ReflectionException
1921
+	 * @see EEM_Base::delete_permanently
1922
+	 */
1923
+	public function delete($query_params, $allow_blocking = true)
1924
+	{
1925
+		return $this->delete_permanently($query_params, $allow_blocking);
1926
+	}
1927
+
1928
+
1929
+	/**
1930
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
1931
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1932
+	 * as archived, not actually deleted
1933
+	 *
1934
+	 * @param array   $query_params
1935
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1936
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1937
+	 *                                deletes regardless of other objects which may depend on it. Its generally
1938
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1939
+	 *                                DB
1940
+	 * @return int how many rows got deleted
1941
+	 * @throws EE_Error
1942
+	 * @throws ReflectionException
1943
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1944
+	 */
1945
+	public function delete_permanently($query_params, $allow_blocking = true)
1946
+	{
1947
+		/**
1948
+		 * Action called just before performing a real deletion query. You can use the
1949
+		 * model and its $query_params to find exactly which items will be deleted
1950
+		 *
1951
+		 * @param EEM_Base $model
1952
+		 * @param array    $query_params
1953
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
1954
+		 *                                 to block (prevent) this deletion
1955
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1956
+		 */
1957
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1958
+		// some MySQL databases may be running safe mode, which may restrict
1959
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
1960
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
1961
+		// to delete them
1962
+		$items_for_deletion           = $this->_get_all_wpdb_results($query_params);
1963
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1964
+		$deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
1965
+			$columns_and_ids_for_deleting
1966
+		);
1967
+		/**
1968
+		 * Allows client code to act on the items being deleted before the query is actually executed.
1969
+		 *
1970
+		 * @param EEM_Base $this                            The model instance being acted on.
1971
+		 * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
1972
+		 * @param bool     $allow_blocking                  @see param description in method phpdoc block.
1973
+		 * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
1974
+		 *                                                  derived from the incoming query parameters.
1975
+		 * @see details on the structure of this array in the phpdocs
1976
+		 *                                                  for the `_get_ids_for_delete_method`
1977
+		 *
1978
+		 */
1979
+		do_action(
1980
+			'AHEE__EEM_Base__delete__before_query',
1981
+			$this,
1982
+			$query_params,
1983
+			$allow_blocking,
1984
+			$columns_and_ids_for_deleting
1985
+		);
1986
+		if ($deletion_where_query_part) {
1987
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
1988
+			$table_aliases    = array_keys($this->_tables);
1989
+			$SQL              = "DELETE "
1990
+								. implode(", ", $table_aliases)
1991
+								. " FROM "
1992
+								. $model_query_info->get_full_join_sql()
1993
+								. " WHERE "
1994
+								. $deletion_where_query_part;
1995
+			$rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
1996
+		} else {
1997
+			$rows_deleted = 0;
1998
+		}
1999
+
2000
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2001
+		// there was no error with the delete query.
2002
+		if (
2003
+			$this->has_primary_key_field()
2004
+			&& $rows_deleted !== false
2005
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2006
+		) {
2007
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2008
+			foreach ($ids_for_removal as $id) {
2009
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2010
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2011
+				}
2012
+			}
2013
+
2014
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2015
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2016
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2017
+			// (although it is possible).
2018
+			// Note this can be skipped by using the provided filter and returning false.
2019
+			if (
2020
+			apply_filters(
2021
+				'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2022
+				! $this instanceof EEM_Extra_Meta,
2023
+				$this
2024
+			)
2025
+			) {
2026
+				EEM_Extra_Meta::instance()->delete_permanently(
2027
+					[
2028
+						0 => [
2029
+							'EXM_type' => $this->get_this_model_name(),
2030
+							'OBJ_ID'   => [
2031
+								'IN',
2032
+								$ids_for_removal,
2033
+							],
2034
+						],
2035
+					]
2036
+				);
2037
+			}
2038
+		}
2039
+
2040
+		/**
2041
+		 * Action called just after performing a real deletion query. Although at this point the
2042
+		 * items should have been deleted
2043
+		 *
2044
+		 * @param EEM_Base $model
2045
+		 * @param array    $query_params
2046
+		 * @param int      $rows_deleted
2047
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2048
+		 */
2049
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2050
+		return $rows_deleted;// how many supposedly got deleted
2051
+	}
2052
+
2053
+
2054
+	/**
2055
+	 * Checks all the relations that throw error messages when there are blocking related objects
2056
+	 * for related model objects. If there are any related model objects on those relations,
2057
+	 * adds an EE_Error, and return true
2058
+	 *
2059
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2060
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2061
+	 *                                                 should be ignored when determining whether there are related
2062
+	 *                                                 model objects which block this model object's deletion. Useful
2063
+	 *                                                 if you know A is related to B and are considering deleting A,
2064
+	 *                                                 but want to see if A has any other objects blocking its deletion
2065
+	 *                                                 before removing the relation between A and B
2066
+	 * @return boolean
2067
+	 * @throws EE_Error
2068
+	 * @throws ReflectionException
2069
+	 */
2070
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2071
+	{
2072
+		// first, if $ignore_this_model_obj was supplied, get its model
2073
+		if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2074
+			$ignored_model = $ignore_this_model_obj->get_model();
2075
+		} else {
2076
+			$ignored_model = null;
2077
+		}
2078
+		// now check all the relations of $this_model_obj_or_id and see if there
2079
+		// are any related model objects blocking it?
2080
+		$is_blocked = false;
2081
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2082
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2083
+				// if $ignore_this_model_obj was supplied, then for the query
2084
+				// on that model needs to be told to ignore $ignore_this_model_obj
2085
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2086
+					$related_model_objects = $relation_obj->get_all_related(
2087
+						$this_model_obj_or_id,
2088
+						[
2089
+							[
2090
+								$ignored_model->get_primary_key_field()->get_name() => [
2091
+									'!=',
2092
+									$ignore_this_model_obj->ID(),
2093
+								],
2094
+							],
2095
+						]
2096
+					);
2097
+				} else {
2098
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2099
+				}
2100
+				if ($related_model_objects) {
2101
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2102
+					$is_blocked = true;
2103
+				}
2104
+			}
2105
+		}
2106
+		return $is_blocked;
2107
+	}
2108
+
2109
+
2110
+	/**
2111
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2112
+	 *
2113
+	 * @param array $row_results_for_deleting
2114
+	 * @param bool  $allow_blocking
2115
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2116
+	 *                              model DOES have a primary_key_field, then the array will be a simple single
2117
+	 *                              dimension array where the key is the fully qualified primary key column and the
2118
+	 *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2119
+	 *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2120
+	 *                              be a two dimensional array where each element is a group of columns and values that
2121
+	 *                              get deleted. Example: array(
2122
+	 *                              0 => array(
2123
+	 *                              'Term_Relationship.object_id' => 1
2124
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2125
+	 *                              ),
2126
+	 *                              1 => array(
2127
+	 *                              'Term_Relationship.object_id' => 1
2128
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2129
+	 *                              )
2130
+	 *                              )
2131
+	 * @throws EE_Error
2132
+	 * @throws ReflectionException
2133
+	 */
2134
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2135
+	{
2136
+		$ids_to_delete_indexed_by_column = [];
2137
+		if ($this->has_primary_key_field()) {
2138
+			$primary_table                   = $this->_get_main_table();
2139
+			$ids_to_delete_indexed_by_column = $query = [];
2140
+			foreach ($row_results_for_deleting as $item_to_delete) {
2141
+				// before we mark this item for deletion,
2142
+				// make sure there's no related entities blocking its deletion (if we're checking)
2143
+				if (
2144
+					$allow_blocking
2145
+					&& $this->delete_is_blocked_by_related_models(
2146
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2147
+					)
2148
+				) {
2149
+					continue;
2150
+				}
2151
+				// primary table deletes
2152
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2153
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2154
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2155
+				}
2156
+			}
2157
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2158
+			$fields = $this->get_combined_primary_key_fields();
2159
+			foreach ($row_results_for_deleting as $item_to_delete) {
2160
+				$ids_to_delete_indexed_by_column_for_row = [];
2161
+				foreach ($fields as $cpk_field) {
2162
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2163
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2164
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2165
+					}
2166
+				}
2167
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2168
+			}
2169
+		} else {
2170
+			// so there's no primary key and no combined key...
2171
+			// sorry, can't help you
2172
+			throw new EE_Error(
2173
+				sprintf(
2174
+					esc_html__(
2175
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2176
+						"event_espresso"
2177
+					),
2178
+					get_class($this)
2179
+				)
2180
+			);
2181
+		}
2182
+		return $ids_to_delete_indexed_by_column;
2183
+	}
2184
+
2185
+
2186
+	/**
2187
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2188
+	 * the corresponding query_part for the query performing the delete.
2189
+	 *
2190
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2191
+	 * @return string
2192
+	 * @throws EE_Error
2193
+	 */
2194
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2195
+	{
2196
+		$query_part = '';
2197
+		if (empty($ids_to_delete_indexed_by_column)) {
2198
+			return $query_part;
2199
+		} elseif ($this->has_primary_key_field()) {
2200
+			$query = [];
2201
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2202
+				// make sure we have unique $ids
2203
+				$ids     = array_unique($ids);
2204
+				$query[] = $column . ' IN(' . implode(',', $ids) . ')';
2205
+			}
2206
+			$query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2207
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2208
+			$ways_to_identify_a_row = [];
2209
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2210
+				$values_for_each_combined_primary_key_for_a_row = [];
2211
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2212
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2213
+				}
2214
+				$ways_to_identify_a_row[] = '('
2215
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2216
+											. ')';
2217
+			}
2218
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2219
+		}
2220
+		return $query_part;
2221
+	}
2222
+
2223
+
2224
+	/**
2225
+	 * Gets the model field by the fully qualified name
2226
+	 *
2227
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2228
+	 * @return EE_Model_Field_Base
2229
+	 * @throws EE_Error
2230
+	 */
2231
+	public function get_field_by_column($qualified_column_name)
2232
+	{
2233
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2234
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2235
+				return $field_obj;
2236
+			}
2237
+		}
2238
+		throw new EE_Error(
2239
+			sprintf(
2240
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2241
+				$this->get_this_model_name(),
2242
+				$qualified_column_name
2243
+			)
2244
+		);
2245
+	}
2246
+
2247
+
2248
+	/**
2249
+	 * Count all the rows that match criteria the model query params.
2250
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2251
+	 * column
2252
+	 *
2253
+	 * @param array  $query_params
2254
+	 * @param string $field_to_count field on model to count by (not column name)
2255
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2256
+	 *                               that by the setting $distinct to TRUE;
2257
+	 * @return int
2258
+	 * @throws EE_Error
2259
+	 * @throws ReflectionException
2260
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2261
+	 */
2262
+	public function count($query_params = [], $field_to_count = null, $distinct = false)
2263
+	{
2264
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2265
+		if ($field_to_count) {
2266
+			$field_obj       = $this->field_settings_for($field_to_count);
2267
+			$column_to_count = $field_obj->get_qualified_column();
2268
+		} elseif ($this->has_primary_key_field()) {
2269
+			$pk_field_obj    = $this->get_primary_key_field();
2270
+			$column_to_count = $pk_field_obj->get_qualified_column();
2271
+		} else {
2272
+			// there's no primary key
2273
+			// if we're counting distinct items, and there's no primary key,
2274
+			// we need to list out the columns for distinction;
2275
+			// otherwise we can just use star
2276
+			if ($distinct) {
2277
+				$columns_to_use = [];
2278
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2279
+					$columns_to_use[] = $field_obj->get_qualified_column();
2280
+				}
2281
+				$column_to_count = implode(',', $columns_to_use);
2282
+			} else {
2283
+				$column_to_count = '*';
2284
+			}
2285
+		}
2286
+		$column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2287
+		$SQL             =
2288
+			"SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2289
+		return (int)$this->_do_wpdb_query('get_var', [$SQL]);
2290
+	}
2291
+
2292
+
2293
+	/**
2294
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2295
+	 *
2296
+	 * @param array  $query_params
2297
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2298
+	 * @return float
2299
+	 * @throws EE_Error
2300
+	 * @throws ReflectionException
2301
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2302
+	 */
2303
+	public function sum($query_params, $field_to_sum = null)
2304
+	{
2305
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2306
+		if ($field_to_sum) {
2307
+			$field_obj = $this->field_settings_for($field_to_sum);
2308
+		} else {
2309
+			$field_obj = $this->get_primary_key_field();
2310
+		}
2311
+		$column_to_count = $field_obj->get_qualified_column();
2312
+		$SQL             =
2313
+			"SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2314
+		$return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2315
+		$data_type       = $field_obj->get_wpdb_data_type();
2316
+		if ($data_type === '%d' || $data_type === '%s') {
2317
+			return (float)$return_value;
2318
+		}
2319
+		// must be %f
2320
+		return (float)$return_value;
2321
+	}
2322
+
2323
+
2324
+	/**
2325
+	 * Just calls the specified method on $wpdb with the given arguments
2326
+	 * Consolidates a little extra error handling code
2327
+	 *
2328
+	 * @param string $wpdb_method
2329
+	 * @param array  $arguments_to_provide
2330
+	 * @return mixed
2331
+	 * @throws EE_Error
2332
+	 * @global wpdb  $wpdb
2333
+	 */
2334
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2335
+	{
2336
+		// if we're in maintenance mode level 2, DON'T run any queries
2337
+		// because level 2 indicates the database needs updating and
2338
+		// is probably out of sync with the code
2339
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2340
+			throw new EE_Error(
2341
+				sprintf(
2342
+					esc_html__(
2343
+						"Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2344
+						"event_espresso"
2345
+					)
2346
+				)
2347
+			);
2348
+		}
2349
+		global $wpdb;
2350
+		if (! method_exists($wpdb, $wpdb_method)) {
2351
+			throw new EE_Error(
2352
+				sprintf(
2353
+					esc_html__(
2354
+						'There is no method named "%s" on Wordpress\' $wpdb object',
2355
+						'event_espresso'
2356
+					),
2357
+					$wpdb_method
2358
+				)
2359
+			);
2360
+		}
2361
+		$old_show_errors_value = $wpdb->show_errors;
2362
+		if (WP_DEBUG) {
2363
+			$wpdb->show_errors(false);
2364
+		}
2365
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2366
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2367
+		if (WP_DEBUG) {
2368
+			$wpdb->show_errors($old_show_errors_value);
2369
+			if (! empty($wpdb->last_error)) {
2370
+				throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2371
+			}
2372
+			if ($result === false) {
2373
+				throw new EE_Error(
2374
+					sprintf(
2375
+						esc_html__(
2376
+							'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2377
+							'event_espresso'
2378
+						),
2379
+						$wpdb_method,
2380
+						var_export($arguments_to_provide, true)
2381
+					)
2382
+				);
2383
+			}
2384
+		} elseif ($result === false) {
2385
+			EE_Error::add_error(
2386
+				sprintf(
2387
+					esc_html__(
2388
+						'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2389
+						'event_espresso'
2390
+					),
2391
+					$wpdb_method,
2392
+					var_export($arguments_to_provide, true),
2393
+					$wpdb->last_error
2394
+				),
2395
+				__FILE__,
2396
+				__FUNCTION__,
2397
+				__LINE__
2398
+			);
2399
+		}
2400
+		return $result;
2401
+	}
2402
+
2403
+
2404
+	/**
2405
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2406
+	 * and if there's an error tries to verify the DB is correct. Uses
2407
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2408
+	 * we should try to fix the EE core db, the addons, or just give up
2409
+	 *
2410
+	 * @param string $wpdb_method
2411
+	 * @param array  $arguments_to_provide
2412
+	 * @return mixed
2413
+	 * @throws EE_Error
2414
+	 */
2415
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2416
+	{
2417
+		global $wpdb;
2418
+		$wpdb->last_error = null;
2419
+		$result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2420
+		// was there an error running the query? but we don't care on new activations
2421
+		// (we're going to setup the DB anyway on new activations)
2422
+		if (
2423
+			($result === false || ! empty($wpdb->last_error))
2424
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2425
+		) {
2426
+			switch (EEM_Base::$_db_verification_level) {
2427
+				case EEM_Base::db_verified_none:
2428
+					// let's double-check core's DB
2429
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2430
+					break;
2431
+				case EEM_Base::db_verified_core:
2432
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2433
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2434
+					break;
2435
+				case EEM_Base::db_verified_addons:
2436
+					// ummmm... you in trouble
2437
+					return $result;
2438
+			}
2439
+			if (! empty($error_message)) {
2440
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2441
+				trigger_error($error_message);
2442
+			}
2443
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2444
+		}
2445
+		return $result;
2446
+	}
2447
+
2448
+
2449
+	/**
2450
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2451
+	 * EEM_Base::$_db_verification_level
2452
+	 *
2453
+	 * @param string $wpdb_method
2454
+	 * @param array  $arguments_to_provide
2455
+	 * @return string
2456
+	 * @throws EE_Error
2457
+	 */
2458
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2459
+	{
2460
+		global $wpdb;
2461
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2462
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2463
+		$error_message                    = sprintf(
2464
+			esc_html__(
2465
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2466
+				'event_espresso'
2467
+			),
2468
+			$wpdb->last_error,
2469
+			$wpdb_method,
2470
+			wp_json_encode($arguments_to_provide)
2471
+		);
2472
+		EE_System::instance()->initialize_db_if_no_migrations_required();
2473
+		return $error_message;
2474
+	}
2475
+
2476
+
2477
+	/**
2478
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2479
+	 * EEM_Base::$_db_verification_level
2480
+	 *
2481
+	 * @param $wpdb_method
2482
+	 * @param $arguments_to_provide
2483
+	 * @return string
2484
+	 * @throws EE_Error
2485
+	 */
2486
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2487
+	{
2488
+		global $wpdb;
2489
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2490
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2491
+		$error_message                    = sprintf(
2492
+			esc_html__(
2493
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2494
+				'event_espresso'
2495
+			),
2496
+			$wpdb->last_error,
2497
+			$wpdb_method,
2498
+			wp_json_encode($arguments_to_provide)
2499
+		);
2500
+		EE_System::instance()->initialize_addons();
2501
+		return $error_message;
2502
+	}
2503
+
2504
+
2505
+	/**
2506
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2507
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2508
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2509
+	 * ..."
2510
+	 *
2511
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2512
+	 * @return string
2513
+	 */
2514
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2515
+	{
2516
+		return " FROM " . $model_query_info->get_full_join_sql() .
2517
+			   $model_query_info->get_where_sql() .
2518
+			   $model_query_info->get_group_by_sql() .
2519
+			   $model_query_info->get_having_sql() .
2520
+			   $model_query_info->get_order_by_sql() .
2521
+			   $model_query_info->get_limit_sql();
2522
+	}
2523
+
2524
+
2525
+	/**
2526
+	 * Set to easily debug the next X queries ran from this model.
2527
+	 *
2528
+	 * @param int $count
2529
+	 */
2530
+	public function show_next_x_db_queries($count = 1)
2531
+	{
2532
+		$this->_show_next_x_db_queries = $count;
2533
+	}
2534
+
2535
+
2536
+	/**
2537
+	 * @param $sql_query
2538
+	 */
2539
+	public function show_db_query_if_previously_requested($sql_query)
2540
+	{
2541
+		if ($this->_show_next_x_db_queries > 0) {
2542
+			echo $sql_query;
2543
+			$this->_show_next_x_db_queries--;
2544
+		}
2545
+	}
2546
+
2547
+
2548
+	/**
2549
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2550
+	 * There are the 3 cases:
2551
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2552
+	 * $otherModelObject has no ID, it is first saved.
2553
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2554
+	 * has no ID, it is first saved.
2555
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2556
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2557
+	 * join table
2558
+	 *
2559
+	 * @param EE_Base_Class                     /int $thisModelObject
2560
+	 * @param EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2561
+	 * @param string $relationName                     , key in EEM_Base::_relations
2562
+	 *                                                 an attendee to a group, you also want to specify which role they
2563
+	 *                                                 will have in that group. So you would use this parameter to
2564
+	 *                                                 specify array('role-column-name'=>'role-id')
2565
+	 * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2566
+	 *                                                 to for relation to methods that allow you to further specify
2567
+	 *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2568
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2569
+	 *                                                 because these will be inserted in any new rows created as well.
2570
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2571
+	 * @throws EE_Error
2572
+	 */
2573
+	public function add_relationship_to(
2574
+		$id_or_obj,
2575
+		$other_model_id_or_obj,
2576
+		$relationName,
2577
+		$extra_join_model_fields_n_values = []
2578
+	) {
2579
+		$relation_obj = $this->related_settings_for($relationName);
2580
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2581
+	}
2582
+
2583
+
2584
+	/**
2585
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2586
+	 * There are the 3 cases:
2587
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2588
+	 * error
2589
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2590
+	 * an error
2591
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2592
+	 *
2593
+	 * @param EE_Base_Class /int $id_or_obj
2594
+	 * @param EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2595
+	 * @param string $relationName key in EEM_Base::_relations
2596
+	 * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2597
+	 *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2598
+	 *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2599
+	 *                             because these will be inserted in any new rows created as well.
2600
+	 * @return boolean of success
2601
+	 * @throws EE_Error
2602
+	 */
2603
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2604
+	{
2605
+		$relation_obj = $this->related_settings_for($relationName);
2606
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2607
+	}
2608
+
2609
+
2610
+	/**
2611
+	 * @param mixed  $id_or_obj
2612
+	 * @param string $relationName
2613
+	 * @param array  $where_query_params
2614
+	 * @param EE_Base_Class[] objects to which relations were removed
2615
+	 * @return EE_Base_Class[]
2616
+	 * @throws EE_Error
2617
+	 */
2618
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2619
+	{
2620
+		$relation_obj = $this->related_settings_for($relationName);
2621
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2622
+	}
2623
+
2624
+
2625
+	/**
2626
+	 * Gets all the related items of the specified $model_name, using $query_params.
2627
+	 * Note: by default, we remove the "default query params"
2628
+	 * because we want to get even deleted items etc.
2629
+	 *
2630
+	 * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2631
+	 * @param string $model_name   like 'Event', 'Registration', etc. always singular
2632
+	 * @param array  $query_params see github link below for more info
2633
+	 * @return EE_Base_Class[]
2634
+	 * @throws EE_Error
2635
+	 * @throws ReflectionException
2636
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2637
+	 */
2638
+	public function get_all_related($id_or_obj, $model_name, $query_params = null)
2639
+	{
2640
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2641
+		$relation_settings = $this->related_settings_for($model_name);
2642
+		return $relation_settings->get_all_related($model_obj, $query_params);
2643
+	}
2644
+
2645
+
2646
+	/**
2647
+	 * Deletes all the model objects across the relation indicated by $model_name
2648
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2649
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2650
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2651
+	 *
2652
+	 * @param EE_Base_Class|int|string $id_or_obj
2653
+	 * @param string                   $model_name
2654
+	 * @param array                    $query_params
2655
+	 * @return int how many deleted
2656
+	 * @throws EE_Error
2657
+	 * @throws ReflectionException
2658
+	 */
2659
+	public function delete_related($id_or_obj, $model_name, $query_params = [])
2660
+	{
2661
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2662
+		$relation_settings = $this->related_settings_for($model_name);
2663
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2664
+	}
2665
+
2666
+
2667
+	/**
2668
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2669
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2670
+	 * the model objects can't be hard deleted because of blocking related model objects,
2671
+	 * just does a soft-delete on them instead.
2672
+	 *
2673
+	 * @param EE_Base_Class|int|string $id_or_obj
2674
+	 * @param string                   $model_name
2675
+	 * @param array                    $query_params
2676
+	 * @return int how many deleted
2677
+	 * @throws EE_Error
2678
+	 * @throws ReflectionException
2679
+	 */
2680
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2681
+	{
2682
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2683
+		$relation_settings = $this->related_settings_for($model_name);
2684
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2685
+	}
2686
+
2687
+
2688
+	/**
2689
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2690
+	 * unless otherwise specified in the $query_params
2691
+	 *
2692
+	 * @param int             /EE_Base_Class $id_or_obj
2693
+	 * @param string $model_name     like 'Event', or 'Registration'
2694
+	 * @param array  $query_params
2695
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2696
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2697
+	 *                               that by the setting $distinct to TRUE;
2698
+	 * @return int
2699
+	 * @throws EE_Error
2700
+	 * @throws ReflectionException
2701
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2702
+	 */
2703
+	public function count_related(
2704
+		$id_or_obj,
2705
+		$model_name,
2706
+		$query_params = [],
2707
+		$field_to_count = null,
2708
+		$distinct = false
2709
+	) {
2710
+		$related_model = $this->get_related_model_obj($model_name);
2711
+		// we're just going to use the query params on the related model's normal get_all query,
2712
+		// except add a condition to say to match the current mod
2713
+		if (! isset($query_params['default_where_conditions'])) {
2714
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2715
+		}
2716
+		$this_model_name                                                 = $this->get_this_model_name();
2717
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2718
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2719
+		return $related_model->count($query_params, $field_to_count, $distinct);
2720
+	}
2721
+
2722
+
2723
+	/**
2724
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2725
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2726
+	 *
2727
+	 * @param int           /EE_Base_Class $id_or_obj
2728
+	 * @param string $model_name   like 'Event', or 'Registration'
2729
+	 * @param array  $query_params
2730
+	 * @param string $field_to_sum name of field to count by. By default, uses primary key
2731
+	 * @return float
2732
+	 * @throws EE_Error
2733
+	 * @throws ReflectionException
2734
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2735
+	 */
2736
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2737
+	{
2738
+		$related_model = $this->get_related_model_obj($model_name);
2739
+		if (! is_array($query_params)) {
2740
+			EE_Error::doing_it_wrong(
2741
+				'EEM_Base::sum_related',
2742
+				sprintf(
2743
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2744
+					gettype($query_params)
2745
+				),
2746
+				'4.6.0'
2747
+			);
2748
+			$query_params = [];
2749
+		}
2750
+		// we're just going to use the query params on the related model's normal get_all query,
2751
+		// except add a condition to say to match the current mod
2752
+		if (! isset($query_params['default_where_conditions'])) {
2753
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2754
+		}
2755
+		$this_model_name                                                 = $this->get_this_model_name();
2756
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2757
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2758
+		return $related_model->sum($query_params, $field_to_sum);
2759
+	}
2760
+
2761
+
2762
+	/**
2763
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2764
+	 * $modelObject
2765
+	 *
2766
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2767
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2768
+	 * @param array               $query_params     see github link below for more info
2769
+	 * @return EE_Base_Class
2770
+	 * @throws EE_Error
2771
+	 * @throws ReflectionException
2772
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2773
+	 */
2774
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2775
+	{
2776
+		$query_params['limit'] = 1;
2777
+		$results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2778
+		if ($results) {
2779
+			return array_shift($results);
2780
+		}
2781
+		return null;
2782
+	}
2783
+
2784
+
2785
+	/**
2786
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2787
+	 *
2788
+	 * @return string
2789
+	 */
2790
+	public function get_this_model_name()
2791
+	{
2792
+		return str_replace("EEM_", "", get_class($this));
2793
+	}
2794
+
2795
+
2796
+	/**
2797
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2798
+	 *
2799
+	 * @return EE_Any_Foreign_Model_Name_Field
2800
+	 * @throws EE_Error
2801
+	 */
2802
+	public function get_field_containing_related_model_name()
2803
+	{
2804
+		foreach ($this->field_settings(true) as $field) {
2805
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2806
+				$field_with_model_name = $field;
2807
+			}
2808
+		}
2809
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2810
+			throw new EE_Error(
2811
+				sprintf(
2812
+					esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2813
+					$this->get_this_model_name()
2814
+				)
2815
+			);
2816
+		}
2817
+		return $field_with_model_name;
2818
+	}
2819
+
2820
+
2821
+	/**
2822
+	 * Inserts a new entry into the database, for each table.
2823
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2824
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2825
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2826
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2827
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2828
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2829
+	 *
2830
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2831
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2832
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2833
+	 *                              of EEM_Base)
2834
+	 * @return int|string new primary key on main table that got inserted
2835
+	 * @throws EE_Error
2836
+	 * @throws ReflectionException
2837
+	 */
2838
+	public function insert($field_n_values)
2839
+	{
2840
+		/**
2841
+		 * Filters the fields and their values before inserting an item using the models
2842
+		 *
2843
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2844
+		 * @param EEM_Base $model           the model used
2845
+		 */
2846
+		$field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2847
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2848
+			$main_table = $this->_get_main_table();
2849
+			$new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2850
+			if ($new_id !== false) {
2851
+				foreach ($this->_get_other_tables() as $other_table) {
2852
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2853
+				}
2854
+			}
2855
+			/**
2856
+			 * Done just after attempting to insert a new model object
2857
+			 *
2858
+			 * @param EEM_Base $model           used
2859
+			 * @param array    $fields_n_values fields and their values
2860
+			 * @param int|string the              ID of the newly-inserted model object
2861
+			 */
2862
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2863
+			return $new_id;
2864
+		}
2865
+		return false;
2866
+	}
2867
+
2868
+
2869
+	/**
2870
+	 * Checks that the result would satisfy the unique indexes on this model
2871
+	 *
2872
+	 * @param array  $field_n_values
2873
+	 * @param string $action
2874
+	 * @return boolean
2875
+	 * @throws EE_Error
2876
+	 * @throws ReflectionException
2877
+	 */
2878
+	protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2879
+	{
2880
+		foreach ($this->unique_indexes() as $index_name => $index) {
2881
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2882
+			if ($this->exists([$uniqueness_where_params])) {
2883
+				EE_Error::add_error(
2884
+					sprintf(
2885
+						esc_html__(
2886
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2887
+							"event_espresso"
2888
+						),
2889
+						$action,
2890
+						$this->_get_class_name(),
2891
+						$index_name,
2892
+						implode(",", $index->field_names()),
2893
+						http_build_query($uniqueness_where_params)
2894
+					),
2895
+					__FILE__,
2896
+					__FUNCTION__,
2897
+					__LINE__
2898
+				);
2899
+				return false;
2900
+			}
2901
+		}
2902
+		return true;
2903
+	}
2904
+
2905
+
2906
+	/**
2907
+	 * Checks the database for an item that conflicts (ie, if this item were
2908
+	 * saved to the DB would break some uniqueness requirement, like a primary key
2909
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
2910
+	 * can be either an EE_Base_Class or an array of fields n values
2911
+	 *
2912
+	 * @param EE_Base_Class|array $obj_or_fields_array
2913
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
2914
+	 *                                                 when looking for conflicts
2915
+	 *                                                 (ie, if false, we ignore the model object's primary key
2916
+	 *                                                 when finding "conflicts". If true, it's also considered).
2917
+	 *                                                 Only works for INT primary key,
2918
+	 *                                                 STRING primary keys cannot be ignored
2919
+	 * @return EE_Base_Class|array
2920
+	 * @throws EE_Error
2921
+	 * @throws ReflectionException
2922
+	 */
2923
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2924
+	{
2925
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
2926
+			$fields_n_values = $obj_or_fields_array->model_field_array();
2927
+		} elseif (is_array($obj_or_fields_array)) {
2928
+			$fields_n_values = $obj_or_fields_array;
2929
+		} else {
2930
+			throw new EE_Error(
2931
+				sprintf(
2932
+					esc_html__(
2933
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2934
+						"event_espresso"
2935
+					),
2936
+					get_class($this),
2937
+					$obj_or_fields_array
2938
+				)
2939
+			);
2940
+		}
2941
+		$query_params = [];
2942
+		if (
2943
+			$this->has_primary_key_field()
2944
+			&& ($include_primary_key
2945
+				|| $this->get_primary_key_field()
2946
+				   instanceof
2947
+				   EE_Primary_Key_String_Field)
2948
+			&& isset($fields_n_values[ $this->primary_key_name() ])
2949
+		) {
2950
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2951
+		}
2952
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2953
+			$uniqueness_where_params                              =
2954
+				array_intersect_key($fields_n_values, $unique_index->fields());
2955
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2956
+		}
2957
+		// if there is nothing to base this search on, then we shouldn't find anything
2958
+		if (empty($query_params)) {
2959
+			return [];
2960
+		}
2961
+		return $this->get_one($query_params);
2962
+	}
2963
+
2964
+
2965
+	/**
2966
+	 * Like count, but is optimized and returns a boolean instead of an int
2967
+	 *
2968
+	 * @param array $query_params
2969
+	 * @return boolean
2970
+	 * @throws EE_Error
2971
+	 * @throws ReflectionException
2972
+	 */
2973
+	public function exists($query_params)
2974
+	{
2975
+		$query_params['limit'] = 1;
2976
+		return $this->count($query_params) > 0;
2977
+	}
2978
+
2979
+
2980
+	/**
2981
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
2982
+	 *
2983
+	 * @param int|string $id
2984
+	 * @return boolean
2985
+	 * @throws EE_Error
2986
+	 * @throws ReflectionException
2987
+	 */
2988
+	public function exists_by_ID($id)
2989
+	{
2990
+		return $this->exists(
2991
+			[
2992
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
2993
+				[
2994
+					$this->primary_key_name() => $id,
2995
+				],
2996
+			]
2997
+		);
2998
+	}
2999
+
3000
+
3001
+	/**
3002
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3003
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3004
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3005
+	 * on the main table)
3006
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3007
+	 * cases where we want to call it directly rather than via insert().
3008
+	 *
3009
+	 * @access   protected
3010
+	 * @param EE_Table_Base $table
3011
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3012
+	 *                                       float
3013
+	 * @param int           $new_id          for now we assume only int keys
3014
+	 * @return int ID of new row inserted, or FALSE on failure
3015
+	 * @throws EE_Error
3016
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3017
+	 */
3018
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3019
+	{
3020
+		global $wpdb;
3021
+		$insertion_col_n_values = [];
3022
+		$format_for_insertion   = [];
3023
+		$fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3024
+		foreach ($fields_on_table as $field_name => $field_obj) {
3025
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3026
+			if ($field_obj->is_auto_increment()) {
3027
+				continue;
3028
+			}
3029
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3030
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3031
+			if ($prepared_value !== null) {
3032
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3033
+				$format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3034
+			}
3035
+		}
3036
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3037
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3038
+			// so add the fk to the main table as a column
3039
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3040
+			$format_for_insertion[]                              =
3041
+				'%d';// yes right now we're only allowing these foreign keys to be INTs
3042
+		}
3043
+		// insert the new entry
3044
+		$result = $this->_do_wpdb_query(
3045
+			'insert',
3046
+			[$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3047
+		);
3048
+		if ($result === false) {
3049
+			return false;
3050
+		}
3051
+		// ok, now what do we return for the ID of the newly-inserted thing?
3052
+		if ($this->has_primary_key_field()) {
3053
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3054
+				return $wpdb->insert_id;
3055
+			}
3056
+			// it's not an auto-increment primary key, so
3057
+			// it must have been supplied
3058
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3059
+		}
3060
+		// we can't return a  primary key because there is none. instead return
3061
+		// a unique string indicating this model
3062
+		return $this->get_index_primary_key_string($fields_n_values);
3063
+	}
3064
+
3065
+
3066
+	/**
3067
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3068
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3069
+	 * and there is no default, we pass it along. WPDB will take care of it)
3070
+	 *
3071
+	 * @param EE_Model_Field_Base $field_obj
3072
+	 * @param array               $fields_n_values
3073
+	 * @return mixed string|int|float depending on what the table column will be expecting
3074
+	 * @throws EE_Error
3075
+	 */
3076
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3077
+	{
3078
+		// if this field doesn't allow nullable, don't allow it
3079
+		if (
3080
+			! $field_obj->is_nullable()
3081
+			&& (
3082
+				! isset($fields_n_values[ $field_obj->get_name() ])
3083
+				|| $fields_n_values[ $field_obj->get_name() ] === null
3084
+			)
3085
+		) {
3086
+			$fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3087
+		}
3088
+		$unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3089
+			? $fields_n_values[ $field_obj->get_name() ]
3090
+			: null;
3091
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3092
+	}
3093
+
3094
+
3095
+	/**
3096
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3097
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3098
+	 * the field's prepare_for_set() method.
3099
+	 *
3100
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3101
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3102
+	 *                                   top of file)
3103
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3104
+	 *                                   $value is a custom selection
3105
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3106
+	 */
3107
+	private function _prepare_value_for_use_in_db($value, $field)
3108
+	{
3109
+		if ($field && $field instanceof EE_Model_Field_Base) {
3110
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3111
+			switch ($this->_values_already_prepared_by_model_object) {
3112
+				/** @noinspection PhpMissingBreakStatementInspection */
3113
+				case self::not_prepared_by_model_object:
3114
+					$value = $field->prepare_for_set($value);
3115
+				// purposefully left out "return"
3116
+				case self::prepared_by_model_object:
3117
+					/** @noinspection SuspiciousAssignmentsInspection */
3118
+					$value = $field->prepare_for_use_in_db($value);
3119
+				case self::prepared_for_use_in_db:
3120
+					// leave the value alone
3121
+			}
3122
+			return $value;
3123
+			// phpcs:enable
3124
+		}
3125
+		return $value;
3126
+	}
3127
+
3128
+
3129
+	/**
3130
+	 * Returns the main table on this model
3131
+	 *
3132
+	 * @return EE_Primary_Table
3133
+	 * @throws EE_Error
3134
+	 */
3135
+	protected function _get_main_table()
3136
+	{
3137
+		foreach ($this->_tables as $table) {
3138
+			if ($table instanceof EE_Primary_Table) {
3139
+				return $table;
3140
+			}
3141
+		}
3142
+		throw new EE_Error(
3143
+			sprintf(
3144
+				esc_html__(
3145
+					'There are no main tables on %s. They should be added to _tables array in the constructor',
3146
+					'event_espresso'
3147
+				),
3148
+				get_class($this)
3149
+			)
3150
+		);
3151
+	}
3152
+
3153
+
3154
+	/**
3155
+	 * table
3156
+	 * returns EE_Primary_Table table name
3157
+	 *
3158
+	 * @return string
3159
+	 * @throws EE_Error
3160
+	 */
3161
+	public function table()
3162
+	{
3163
+		return $this->_get_main_table()->get_table_name();
3164
+	}
3165
+
3166
+
3167
+	/**
3168
+	 * table
3169
+	 * returns first EE_Secondary_Table table name
3170
+	 *
3171
+	 * @return string
3172
+	 */
3173
+	public function second_table()
3174
+	{
3175
+		// grab second table from tables array
3176
+		$second_table = end($this->_tables);
3177
+		return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3178
+	}
3179
+
3180
+
3181
+	/**
3182
+	 * get_table_obj_by_alias
3183
+	 * returns table name given it's alias
3184
+	 *
3185
+	 * @param string $table_alias
3186
+	 * @return EE_Primary_Table | EE_Secondary_Table
3187
+	 */
3188
+	public function get_table_obj_by_alias($table_alias = '')
3189
+	{
3190
+		return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3191
+	}
3192
+
3193
+
3194
+	/**
3195
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3196
+	 *
3197
+	 * @return EE_Secondary_Table[]
3198
+	 */
3199
+	protected function _get_other_tables()
3200
+	{
3201
+		$other_tables = [];
3202
+		foreach ($this->_tables as $table_alias => $table) {
3203
+			if ($table instanceof EE_Secondary_Table) {
3204
+				$other_tables[ $table_alias ] = $table;
3205
+			}
3206
+		}
3207
+		return $other_tables;
3208
+	}
3209
+
3210
+
3211
+	/**
3212
+	 * Finds all the fields that correspond to the given table
3213
+	 *
3214
+	 * @param string $table_alias , array key in EEM_Base::_tables
3215
+	 * @return EE_Model_Field_Base[]
3216
+	 */
3217
+	public function _get_fields_for_table($table_alias)
3218
+	{
3219
+		return $this->_fields[ $table_alias ];
3220
+	}
3221
+
3222
+
3223
+	/**
3224
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3225
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3226
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3227
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3228
+	 * related Registration, Transaction, and Payment models.
3229
+	 *
3230
+	 * @param array $query_params see github link below for more info
3231
+	 * @return EE_Model_Query_Info_Carrier
3232
+	 * @throws EE_Error
3233
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3234
+	 */
3235
+	public function _extract_related_models_from_query($query_params)
3236
+	{
3237
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3238
+		if (array_key_exists(0, $query_params)) {
3239
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3240
+		}
3241
+		if (array_key_exists('group_by', $query_params)) {
3242
+			if (is_array($query_params['group_by'])) {
3243
+				$this->_extract_related_models_from_sub_params_array_values(
3244
+					$query_params['group_by'],
3245
+					$query_info_carrier,
3246
+					'group_by'
3247
+				);
3248
+			} elseif (! empty($query_params['group_by'])) {
3249
+				$this->_extract_related_model_info_from_query_param(
3250
+					$query_params['group_by'],
3251
+					$query_info_carrier,
3252
+					'group_by'
3253
+				);
3254
+			}
3255
+		}
3256
+		if (array_key_exists('having', $query_params)) {
3257
+			$this->_extract_related_models_from_sub_params_array_keys(
3258
+				$query_params[0],
3259
+				$query_info_carrier,
3260
+				'having'
3261
+			);
3262
+		}
3263
+		if (array_key_exists('order_by', $query_params)) {
3264
+			if (is_array($query_params['order_by'])) {
3265
+				$this->_extract_related_models_from_sub_params_array_keys(
3266
+					$query_params['order_by'],
3267
+					$query_info_carrier,
3268
+					'order_by'
3269
+				);
3270
+			} elseif (! empty($query_params['order_by'])) {
3271
+				$this->_extract_related_model_info_from_query_param(
3272
+					$query_params['order_by'],
3273
+					$query_info_carrier,
3274
+					'order_by'
3275
+				);
3276
+			}
3277
+		}
3278
+		if (array_key_exists('force_join', $query_params)) {
3279
+			$this->_extract_related_models_from_sub_params_array_values(
3280
+				$query_params['force_join'],
3281
+				$query_info_carrier,
3282
+				'force_join'
3283
+			);
3284
+		}
3285
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3286
+		return $query_info_carrier;
3287
+	}
3288
+
3289
+
3290
+	/**
3291
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3292
+	 *
3293
+	 * @param array                       $sub_query_params
3294
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3295
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3296
+	 * @return EE_Model_Query_Info_Carrier
3297
+	 * @throws EE_Error
3298
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3299
+	 */
3300
+	private function _extract_related_models_from_sub_params_array_keys(
3301
+		$sub_query_params,
3302
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3303
+		$query_param_type
3304
+	) {
3305
+		if (! empty($sub_query_params)) {
3306
+			$sub_query_params = (array)$sub_query_params;
3307
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3308
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3309
+				$this->_extract_related_model_info_from_query_param(
3310
+					$param,
3311
+					$model_query_info_carrier,
3312
+					$query_param_type
3313
+				);
3314
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3315
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3316
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3317
+				// of array('Registration.TXN_ID'=>23)
3318
+				$query_param_sans_stars =
3319
+					$this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3320
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3321
+					if (! is_array($possibly_array_of_params)) {
3322
+						throw new EE_Error(
3323
+							sprintf(
3324
+								esc_html__(
3325
+									"You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3326
+									"event_espresso"
3327
+								),
3328
+								$param,
3329
+								$possibly_array_of_params
3330
+							)
3331
+						);
3332
+					}
3333
+					$this->_extract_related_models_from_sub_params_array_keys(
3334
+						$possibly_array_of_params,
3335
+						$model_query_info_carrier,
3336
+						$query_param_type
3337
+					);
3338
+				} elseif (
3339
+					$query_param_type === 0 // ie WHERE
3340
+					&& is_array($possibly_array_of_params)
3341
+					&& isset($possibly_array_of_params[2])
3342
+					&& $possibly_array_of_params[2] == true
3343
+				) {
3344
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3345
+					// indicating that $possible_array_of_params[1] is actually a field name,
3346
+					// from which we should extract query parameters!
3347
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3348
+						throw new EE_Error(
3349
+							sprintf(
3350
+								esc_html__(
3351
+									"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3352
+									"event_espresso"
3353
+								),
3354
+								$query_param_type,
3355
+								implode(",", $possibly_array_of_params)
3356
+							)
3357
+						);
3358
+					}
3359
+					$this->_extract_related_model_info_from_query_param(
3360
+						$possibly_array_of_params[1],
3361
+						$model_query_info_carrier,
3362
+						$query_param_type
3363
+					);
3364
+				}
3365
+			}
3366
+		}
3367
+		return $model_query_info_carrier;
3368
+	}
3369
+
3370
+
3371
+	/**
3372
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3373
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3374
+	 *
3375
+	 * @param array                       $sub_query_params
3376
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3377
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3378
+	 * @return EE_Model_Query_Info_Carrier
3379
+	 * @throws EE_Error
3380
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3381
+	 */
3382
+	private function _extract_related_models_from_sub_params_array_values(
3383
+		$sub_query_params,
3384
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3385
+		$query_param_type
3386
+	) {
3387
+		if (! empty($sub_query_params)) {
3388
+			if (! is_array($sub_query_params)) {
3389
+				throw new EE_Error(
3390
+					sprintf(
3391
+						esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3392
+						$sub_query_params
3393
+					)
3394
+				);
3395
+			}
3396
+			foreach ($sub_query_params as $param) {
3397
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3398
+				$this->_extract_related_model_info_from_query_param(
3399
+					$param,
3400
+					$model_query_info_carrier,
3401
+					$query_param_type
3402
+				);
3403
+			}
3404
+		}
3405
+		return $model_query_info_carrier;
3406
+	}
3407
+
3408
+
3409
+	/**
3410
+	 * Extract all the query parts from  model query params
3411
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3412
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3413
+	 * but use them in a different order. Eg, we need to know what models we are querying
3414
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3415
+	 * other models before we can finalize the where clause SQL.
3416
+	 *
3417
+	 * @param array $query_params see github link below for more info
3418
+	 * @return EE_Model_Query_Info_Carrier
3419
+	 * @throws EE_Error
3420
+	 * @throws ModelConfigurationException
3421
+	 * @throws ReflectionException
3422
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3423
+	 */
3424
+	public function _create_model_query_info_carrier($query_params)
3425
+	{
3426
+		if (! is_array($query_params)) {
3427
+			EE_Error::doing_it_wrong(
3428
+				'EEM_Base::_create_model_query_info_carrier',
3429
+				sprintf(
3430
+					esc_html__(
3431
+						'$query_params should be an array, you passed a variable of type %s',
3432
+						'event_espresso'
3433
+					),
3434
+					gettype($query_params)
3435
+				),
3436
+				'4.6.0'
3437
+			);
3438
+			$query_params = [];
3439
+		}
3440
+		$query_params[0] = isset($query_params[0]) ? $query_params[0] : [];
3441
+		// first check if we should alter the query to account for caps or not
3442
+		// because the caps might require us to do extra joins
3443
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3444
+			$query_params[0] = array_replace_recursive(
3445
+				$query_params[0],
3446
+				$this->caps_where_conditions(
3447
+					$query_params['caps']
3448
+				)
3449
+			);
3450
+		}
3451
+
3452
+		// check if we should alter the query to remove data related to protected
3453
+		// custom post types
3454
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3455
+			$where_param_key_for_password = $this->modelChainAndPassword();
3456
+			// only include if related to a cpt where no password has been set
3457
+			$query_params[0]['OR*nopassword'] = [
3458
+				$where_param_key_for_password       => '',
3459
+				$where_param_key_for_password . '*' => ['IS_NULL'],
3460
+			];
3461
+		}
3462
+		$query_object = $this->_extract_related_models_from_query($query_params);
3463
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3464
+		foreach ($query_params[0] as $key => $value) {
3465
+			if (is_int($key)) {
3466
+				throw new EE_Error(
3467
+					sprintf(
3468
+						esc_html__(
3469
+							"WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3470
+							"event_espresso"
3471
+						),
3472
+						$key,
3473
+						var_export($value, true),
3474
+						var_export($query_params, true),
3475
+						get_class($this)
3476
+					)
3477
+				);
3478
+			}
3479
+		}
3480
+		if (
3481
+			array_key_exists('default_where_conditions', $query_params)
3482
+			&& ! empty($query_params['default_where_conditions'])
3483
+		) {
3484
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3485
+		} else {
3486
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3487
+		}
3488
+		$query_params[0] = array_merge(
3489
+			$this->_get_default_where_conditions_for_models_in_query(
3490
+				$query_object,
3491
+				$use_default_where_conditions,
3492
+				$query_params[0]
3493
+			),
3494
+			$query_params[0]
3495
+		);
3496
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3497
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3498
+		// So we need to setup a subquery and use that for the main join.
3499
+		// Note for now this only works on the primary table for the model.
3500
+		// So for instance, you could set the limit array like this:
3501
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3502
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3503
+			$query_object->set_main_model_join_sql(
3504
+				$this->_construct_limit_join_select(
3505
+					$query_params['on_join_limit'][0],
3506
+					$query_params['on_join_limit'][1]
3507
+				)
3508
+			);
3509
+		}
3510
+		// set limit
3511
+		if (array_key_exists('limit', $query_params)) {
3512
+			if (is_array($query_params['limit'])) {
3513
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3514
+					$e = sprintf(
3515
+						esc_html__(
3516
+							"Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3517
+							"event_espresso"
3518
+						),
3519
+						http_build_query($query_params['limit'])
3520
+					);
3521
+					throw new EE_Error($e . "|" . $e);
3522
+				}
3523
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3524
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3525
+			} elseif (! empty($query_params['limit'])) {
3526
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3527
+			}
3528
+		}
3529
+		// set order by
3530
+		if (array_key_exists('order_by', $query_params)) {
3531
+			if (is_array($query_params['order_by'])) {
3532
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3533
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3534
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3535
+				if (array_key_exists('order', $query_params)) {
3536
+					throw new EE_Error(
3537
+						sprintf(
3538
+							esc_html__(
3539
+								"In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3540
+								"event_espresso"
3541
+							),
3542
+							get_class($this),
3543
+							implode(", ", array_keys($query_params['order_by'])),
3544
+							implode(", ", $query_params['order_by']),
3545
+							$query_params['order']
3546
+						)
3547
+					);
3548
+				}
3549
+				$this->_extract_related_models_from_sub_params_array_keys(
3550
+					$query_params['order_by'],
3551
+					$query_object,
3552
+					'order_by'
3553
+				);
3554
+				// assume it's an array of fields to order by
3555
+				$order_array = [];
3556
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3557
+					$order         = $this->_extract_order($order);
3558
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3559
+				}
3560
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3561
+			} elseif (! empty($query_params['order_by'])) {
3562
+				$this->_extract_related_model_info_from_query_param(
3563
+					$query_params['order_by'],
3564
+					$query_object,
3565
+					'order',
3566
+					$query_params['order_by']
3567
+				);
3568
+				$order = isset($query_params['order'])
3569
+					? $this->_extract_order($query_params['order'])
3570
+					: 'DESC';
3571
+				$query_object->set_order_by_sql(
3572
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3573
+				);
3574
+			}
3575
+		}
3576
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3577
+		if (
3578
+			! array_key_exists('order_by', $query_params)
3579
+			&& array_key_exists('order', $query_params)
3580
+			&& ! empty($query_params['order'])
3581
+		) {
3582
+			$pk_field = $this->get_primary_key_field();
3583
+			$order    = $this->_extract_order($query_params['order']);
3584
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3585
+		}
3586
+		// set group by
3587
+		if (array_key_exists('group_by', $query_params)) {
3588
+			if (is_array($query_params['group_by'])) {
3589
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3590
+				$group_by_array = [];
3591
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3592
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3593
+				}
3594
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3595
+			} elseif (! empty($query_params['group_by'])) {
3596
+				$query_object->set_group_by_sql(
3597
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3598
+				);
3599
+			}
3600
+		}
3601
+		// set having
3602
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3603
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3604
+		}
3605
+		// now, just verify they didn't pass anything wack
3606
+		foreach ($query_params as $query_key => $query_value) {
3607
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3608
+				throw new EE_Error(
3609
+					sprintf(
3610
+						esc_html__(
3611
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3612
+							'event_espresso'
3613
+						),
3614
+						$query_key,
3615
+						get_class($this),
3616
+						//                      print_r( $this->_allowed_query_params, TRUE )
3617
+						implode(',', $this->_allowed_query_params)
3618
+					)
3619
+				);
3620
+			}
3621
+		}
3622
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3623
+		if (empty($main_model_join_sql)) {
3624
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3625
+		}
3626
+		return $query_object;
3627
+	}
3628
+
3629
+
3630
+	/**
3631
+	 * Gets the where conditions that should be imposed on the query based on the
3632
+	 * context (eg reading frontend, backend, edit or delete).
3633
+	 *
3634
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3635
+	 * @return array
3636
+	 * @throws EE_Error
3637
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3638
+	 */
3639
+	public function caps_where_conditions($context = self::caps_read)
3640
+	{
3641
+		EEM_Base::verify_is_valid_cap_context($context);
3642
+		$cap_where_conditions = [];
3643
+		$cap_restrictions     = $this->caps_missing($context);
3644
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3645
+			$cap_where_conditions = array_replace_recursive(
3646
+				$cap_where_conditions,
3647
+				$restriction_if_no_cap->get_default_where_conditions()
3648
+			);
3649
+		}
3650
+		return apply_filters(
3651
+			'FHEE__EEM_Base__caps_where_conditions__return',
3652
+			$cap_where_conditions,
3653
+			$this,
3654
+			$context,
3655
+			$cap_restrictions
3656
+		);
3657
+	}
3658
+
3659
+
3660
+	/**
3661
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3662
+	 * otherwise throws an exception
3663
+	 *
3664
+	 * @param string $should_be_order_string
3665
+	 * @return string either ASC, asc, DESC or desc
3666
+	 * @throws EE_Error
3667
+	 */
3668
+	private function _extract_order($should_be_order_string)
3669
+	{
3670
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3671
+			return $should_be_order_string;
3672
+		}
3673
+		throw new EE_Error(
3674
+			sprintf(
3675
+				esc_html__(
3676
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3677
+					"event_espresso"
3678
+				),
3679
+				get_class($this),
3680
+				$should_be_order_string
3681
+			)
3682
+		);
3683
+	}
3684
+
3685
+
3686
+	/**
3687
+	 * Looks at all the models which are included in this query, and asks each
3688
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3689
+	 * so they can be merged
3690
+	 *
3691
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3692
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3693
+	 *                                                                  'none' means NO default where conditions will
3694
+	 *                                                                  be used AT ALL during this query.
3695
+	 *                                                                  'other_models_only' means default where
3696
+	 *                                                                  conditions from other models will be used, but
3697
+	 *                                                                  not for this primary model. 'all', the default,
3698
+	 *                                                                  means default where conditions will apply as
3699
+	 *                                                                  normal
3700
+	 * @param array                       $where_query_params
3701
+	 * @return array
3702
+	 * @throws EE_Error
3703
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3704
+	 */
3705
+	private function _get_default_where_conditions_for_models_in_query(
3706
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3707
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3708
+		$where_query_params = []
3709
+	) {
3710
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3711
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3712
+			throw new EE_Error(
3713
+				sprintf(
3714
+					esc_html__(
3715
+						"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3716
+						"event_espresso"
3717
+					),
3718
+					$use_default_where_conditions,
3719
+					implode(", ", $allowed_used_default_where_conditions_values)
3720
+				)
3721
+			);
3722
+		}
3723
+		$universal_query_params = [];
3724
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions)) {
3725
+			$universal_query_params = $this->_get_default_where_conditions();
3726
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions)) {
3727
+			$universal_query_params = $this->_get_minimum_where_conditions();
3728
+		}
3729
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3730
+			$related_model = $this->get_related_model_obj($model_name);
3731
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3732
+				$related_model_universal_where_params =
3733
+					$related_model->_get_default_where_conditions($model_relation_path);
3734
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3735
+				$related_model_universal_where_params =
3736
+					$related_model->_get_minimum_where_conditions($model_relation_path);
3737
+			} else {
3738
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3739
+				continue;
3740
+			}
3741
+			$overrides              = $this->_override_defaults_or_make_null_friendly(
3742
+				$related_model_universal_where_params,
3743
+				$where_query_params,
3744
+				$related_model,
3745
+				$model_relation_path
3746
+			);
3747
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3748
+				$universal_query_params,
3749
+				$overrides
3750
+			);
3751
+		}
3752
+		return $universal_query_params;
3753
+	}
3754
+
3755
+
3756
+	/**
3757
+	 * Determines whether or not we should use default where conditions for the model in question
3758
+	 * (this model, or other related models).
3759
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3760
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3761
+	 * We should use default where conditions on related models when they requested to use default where conditions
3762
+	 * on all models, or specifically just on other related models
3763
+	 *
3764
+	 * @param      $default_where_conditions_value
3765
+	 * @param bool $for_this_model false means this is for OTHER related models
3766
+	 * @return bool
3767
+	 */
3768
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3769
+	{
3770
+		return (
3771
+				   $for_this_model
3772
+				   && in_array(
3773
+					   $default_where_conditions_value,
3774
+					   [
3775
+						   EEM_Base::default_where_conditions_all,
3776
+						   EEM_Base::default_where_conditions_this_only,
3777
+						   EEM_Base::default_where_conditions_minimum_others,
3778
+					   ],
3779
+					   true
3780
+				   )
3781
+			   )
3782
+			   || (
3783
+				   ! $for_this_model
3784
+				   && in_array(
3785
+					   $default_where_conditions_value,
3786
+					   [
3787
+						   EEM_Base::default_where_conditions_all,
3788
+						   EEM_Base::default_where_conditions_others_only,
3789
+					   ],
3790
+					   true
3791
+				   )
3792
+			   );
3793
+	}
3794
+
3795
+
3796
+	/**
3797
+	 * Determines whether or not we should use default minimum conditions for the model in question
3798
+	 * (this model, or other related models).
3799
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3800
+	 * where conditions.
3801
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3802
+	 * on this model or others
3803
+	 *
3804
+	 * @param      $default_where_conditions_value
3805
+	 * @param bool $for_this_model false means this is for OTHER related models
3806
+	 * @return bool
3807
+	 */
3808
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3809
+	{
3810
+		return (
3811
+				   $for_this_model
3812
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3813
+			   )
3814
+			   || (
3815
+				   ! $for_this_model
3816
+				   && in_array(
3817
+					   $default_where_conditions_value,
3818
+					   [
3819
+						   EEM_Base::default_where_conditions_minimum_others,
3820
+						   EEM_Base::default_where_conditions_minimum_all,
3821
+					   ],
3822
+					   true
3823
+				   )
3824
+			   );
3825
+	}
3826
+
3827
+
3828
+	/**
3829
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3830
+	 * then we also add a special where condition which allows for that model's primary key
3831
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3832
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3833
+	 *
3834
+	 * @param array    $default_where_conditions
3835
+	 * @param array    $provided_where_conditions
3836
+	 * @param EEM_Base $model
3837
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3838
+	 * @return array
3839
+	 * @throws EE_Error
3840
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3841
+	 */
3842
+	private function _override_defaults_or_make_null_friendly(
3843
+		$default_where_conditions,
3844
+		$provided_where_conditions,
3845
+		$model,
3846
+		$model_relation_path
3847
+	) {
3848
+		$null_friendly_where_conditions = [];
3849
+		$none_overridden                = true;
3850
+		$or_condition_key_for_defaults  = 'OR*' . get_class($model);
3851
+		foreach ($default_where_conditions as $key => $val) {
3852
+			if (isset($provided_where_conditions[ $key ])) {
3853
+				$none_overridden = false;
3854
+			} else {
3855
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3856
+			}
3857
+		}
3858
+		if ($none_overridden && $default_where_conditions) {
3859
+			if ($model->has_primary_key_field()) {
3860
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3861
+																				   . "."
3862
+																				   . $model->primary_key_name() ] =
3863
+					['IS NULL'];
3864
+			}/*else{
3865 3865
                 //@todo NO PK, use other defaults
3866 3866
             }*/
3867
-        }
3868
-        return $null_friendly_where_conditions;
3869
-    }
3870
-
3871
-
3872
-    /**
3873
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3874
-     * default where conditions on all get_all, update, and delete queries done by this model.
3875
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3876
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3877
-     *
3878
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3879
-     * @return array
3880
-     * @throws EE_Error
3881
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3882
-     */
3883
-    private function _get_default_where_conditions($model_relation_path = '')
3884
-    {
3885
-        if ($this->_ignore_where_strategy) {
3886
-            return [];
3887
-        }
3888
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3889
-    }
3890
-
3891
-
3892
-    /**
3893
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3894
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3895
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3896
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3897
-     * Similar to _get_default_where_conditions
3898
-     *
3899
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3900
-     * @return array
3901
-     * @throws EE_Error
3902
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3903
-     */
3904
-    protected function _get_minimum_where_conditions($model_relation_path = '')
3905
-    {
3906
-        if ($this->_ignore_where_strategy) {
3907
-            return [];
3908
-        }
3909
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3910
-    }
3911
-
3912
-
3913
-    /**
3914
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3915
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3916
-     *
3917
-     * @param EE_Model_Query_Info_Carrier $model_query_info
3918
-     * @return string
3919
-     * @throws EE_Error
3920
-     */
3921
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3922
-    {
3923
-        $selects = $this->_get_columns_to_select_for_this_model();
3924
-        foreach (
3925
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3926
-        ) {
3927
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3928
-            $other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3929
-            foreach ($other_model_selects as $key => $value) {
3930
-                $selects[] = $value;
3931
-            }
3932
-        }
3933
-        return implode(", ", $selects);
3934
-    }
3935
-
3936
-
3937
-    /**
3938
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3939
-     * So that's going to be the columns for all the fields on the model
3940
-     *
3941
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
3942
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3943
-     */
3944
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3945
-    {
3946
-        $fields                                       = $this->field_settings();
3947
-        $selects                                      = [];
3948
-        $table_alias_with_model_relation_chain_prefix =
3949
-            EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3950
-                $model_relation_chain,
3951
-                $this->get_this_model_name()
3952
-            );
3953
-        foreach ($fields as $field_obj) {
3954
-            $selects[] = $table_alias_with_model_relation_chain_prefix
3955
-                         . $field_obj->get_table_alias()
3956
-                         . "."
3957
-                         . $field_obj->get_table_column()
3958
-                         . " AS '"
3959
-                         . $table_alias_with_model_relation_chain_prefix
3960
-                         . $field_obj->get_table_alias()
3961
-                         . "."
3962
-                         . $field_obj->get_table_column()
3963
-                         . "'";
3964
-        }
3965
-        // make sure we are also getting the PKs of each table
3966
-        $tables = $this->get_tables();
3967
-        if (count($tables) > 1) {
3968
-            foreach ($tables as $table_obj) {
3969
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3970
-                                       . $table_obj->get_fully_qualified_pk_column();
3971
-                if (! in_array($qualified_pk_column, $selects)) {
3972
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3973
-                }
3974
-            }
3975
-        }
3976
-        return $selects;
3977
-    }
3978
-
3979
-
3980
-    /**
3981
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3982
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3983
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3984
-     * SQL for joining, and the data types
3985
-     *
3986
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
3987
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3988
-     * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
3989
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3990
-     *                                                          column name. We only want model names, eg 'Event.Venue'
3991
-     *                                                          or 'Registration's
3992
-     * @param string|null                 $original_query_param what it originally was (eg
3993
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3994
-     *                                                          matches $query_param
3995
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3996
-     * @throws EE_Error
3997
-     */
3998
-    private function _extract_related_model_info_from_query_param(
3999
-        $query_param,
4000
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4001
-        $query_param_type,
4002
-        $original_query_param = null
4003
-    ) {
4004
-        if ($original_query_param === null) {
4005
-            $original_query_param = $query_param;
4006
-        }
4007
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4008
-        // whether or not to allow logic_query_params like 'NOT','OR', or 'AND'
4009
-        $allow_logic_query_params = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4010
-        $allow_fields             = in_array(
4011
-            $query_param_type,
4012
-            ['where', 'having', 'order_by', 'group_by', 'order', 'custom_selects', 0],
4013
-            true
4014
-        );
4015
-        // check to see if we have a field on this model
4016
-        $this_model_fields = $this->field_settings(true);
4017
-        if (array_key_exists($query_param, $this_model_fields)) {
4018
-            if ($allow_fields) {
4019
-                return;
4020
-            }
4021
-            throw new EE_Error(
4022
-                sprintf(
4023
-                    esc_html__(
4024
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4025
-                        "event_espresso"
4026
-                    ),
4027
-                    $query_param,
4028
-                    get_class($this),
4029
-                    $query_param_type,
4030
-                    $original_query_param
4031
-                )
4032
-            );
4033
-        }
4034
-        // check if this is a special logic query param
4035
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4036
-            if ($allow_logic_query_params) {
4037
-                return;
4038
-            }
4039
-            throw new EE_Error(
4040
-                sprintf(
4041
-                    esc_html__(
4042
-                        'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4043
-                        'event_espresso'
4044
-                    ),
4045
-                    implode('", "', $this->_logic_query_param_keys),
4046
-                    $query_param,
4047
-                    get_class($this),
4048
-                    '<br />',
4049
-                    "\t"
4050
-                    . ' $passed_in_query_info = <pre>'
4051
-                    . print_r($passed_in_query_info, true)
4052
-                    . '</pre>'
4053
-                    . "\n\t"
4054
-                    . ' $query_param_type = '
4055
-                    . $query_param_type
4056
-                    . "\n\t"
4057
-                    . ' $original_query_param = '
4058
-                    . $original_query_param
4059
-                )
4060
-            );
4061
-        }
4062
-        // check if it's a custom selection
4063
-        if (
4064
-            $this->_custom_selections instanceof CustomSelects
4065
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4066
-        ) {
4067
-            return;
4068
-        }
4069
-        // check if has a model name at the beginning
4070
-        // and
4071
-        // check if it's a field on a related model
4072
-        if (
4073
-        $this->extractJoinModelFromQueryParams(
4074
-            $passed_in_query_info,
4075
-            $query_param,
4076
-            $original_query_param,
4077
-            $query_param_type
4078
-        )
4079
-        ) {
4080
-            return;
4081
-        }
4082
-
4083
-        // ok so $query_param didn't start with a model name
4084
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4085
-        // it's wack, that's what it is
4086
-        throw new EE_Error(
4087
-            sprintf(
4088
-                esc_html__(
4089
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4090
-                    "event_espresso"
4091
-                ),
4092
-                $query_param,
4093
-                get_class($this),
4094
-                $query_param_type,
4095
-                $original_query_param
4096
-            )
4097
-        );
4098
-    }
4099
-
4100
-
4101
-    /**
4102
-     * Extracts any possible join model information from the provided possible_join_string.
4103
-     * This method will read the provided $possible_join_string value and determine if there are any possible model
4104
-     * join
4105
-     * parts that should be added to the query.
4106
-     *
4107
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4108
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4109
-     * @param null|string                 $original_query_param
4110
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4111
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4112
-     *                                                           etc.)
4113
-     * @return bool  returns true if a join was added and false if not.
4114
-     * @throws EE_Error
4115
-     */
4116
-    private function extractJoinModelFromQueryParams(
4117
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4118
-        $possible_join_string,
4119
-        $original_query_param,
4120
-        $query_parameter_type
4121
-    ) {
4122
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4123
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4124
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4125
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4126
-                if ($possible_join_string === '') {
4127
-                    // nothing left to $query_param
4128
-                    // we should actually end in a field name, not a model like this!
4129
-                    throw new EE_Error(
4130
-                        sprintf(
4131
-                            esc_html__(
4132
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4133
-                                "event_espresso"
4134
-                            ),
4135
-                            $possible_join_string,
4136
-                            $query_parameter_type,
4137
-                            get_class($this),
4138
-                            $valid_related_model_name
4139
-                        )
4140
-                    );
4141
-                }
4142
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4143
-                $related_model_obj->_extract_related_model_info_from_query_param(
4144
-                    $possible_join_string,
4145
-                    $query_info_carrier,
4146
-                    $query_parameter_type,
4147
-                    $original_query_param
4148
-                );
4149
-                return true;
4150
-            }
4151
-            if ($possible_join_string === $valid_related_model_name) {
4152
-                $this->_add_join_to_model(
4153
-                    $valid_related_model_name,
4154
-                    $query_info_carrier,
4155
-                    $original_query_param
4156
-                );
4157
-                return true;
4158
-            }
4159
-        }
4160
-        return false;
4161
-    }
4162
-
4163
-
4164
-    /**
4165
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4166
-     *
4167
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4168
-     * @throws EE_Error
4169
-     */
4170
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4171
-    {
4172
-        if (
4173
-            $this->_custom_selections instanceof CustomSelects
4174
-            && ($this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4175
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4176
-            )
4177
-        ) {
4178
-            $original_selects = $this->_custom_selections->originalSelects();
4179
-            foreach ($original_selects as $alias => $select_configuration) {
4180
-                $this->extractJoinModelFromQueryParams(
4181
-                    $query_info_carrier,
4182
-                    $select_configuration[0],
4183
-                    $select_configuration[0],
4184
-                    'custom_selects'
4185
-                );
4186
-            }
4187
-        }
4188
-    }
4189
-
4190
-
4191
-    /**
4192
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4193
-     * and store it on $passed_in_query_info
4194
-     *
4195
-     * @param string                      $model_name
4196
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4197
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4198
-     *                                                          model and $model_name. Eg, if we are querying Event,
4199
-     *                                                          and are adding a join to 'Payment' with the original
4200
-     *                                                          query param key
4201
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4202
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4203
-     *                                                          Payment wants to add default query params so that it
4204
-     *                                                          will know what models to prepend onto its default query
4205
-     *                                                          params or in case it wants to rename tables (in case
4206
-     *                                                          there are multiple joins to the same table)
4207
-     * @return void
4208
-     * @throws EE_Error
4209
-     */
4210
-    private function _add_join_to_model(
4211
-        $model_name,
4212
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4213
-        $original_query_param
4214
-    ) {
4215
-        $relation_obj         = $this->related_settings_for($model_name);
4216
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4217
-        // check if the relation is HABTM, because then we're essentially doing two joins
4218
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4219
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4220
-            $join_model_obj = $relation_obj->get_join_model();
4221
-            // replace the model specified with the join model for this relation chain, whi
4222
-            $relation_chain_to_join_model =
4223
-                EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4224
-                    $model_name,
4225
-                    $join_model_obj->get_this_model_name(),
4226
-                    $model_relation_chain
4227
-                );
4228
-            $passed_in_query_info->merge(
4229
-                new EE_Model_Query_Info_Carrier(
4230
-                    [$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4231
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4232
-                )
4233
-            );
4234
-        }
4235
-        // now just join to the other table pointed to by the relation object, and add its data types
4236
-        $passed_in_query_info->merge(
4237
-            new EE_Model_Query_Info_Carrier(
4238
-                [$model_relation_chain => $model_name],
4239
-                $relation_obj->get_join_statement($model_relation_chain)
4240
-            )
4241
-        );
4242
-    }
4243
-
4244
-
4245
-    /**
4246
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4247
-     *
4248
-     * @param array $where_params
4249
-     * @return string of SQL
4250
-     * @throws EE_Error
4251
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4252
-     */
4253
-    private function _construct_where_clause($where_params)
4254
-    {
4255
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4256
-        if ($SQL) {
4257
-            return " WHERE " . $SQL;
4258
-        }
4259
-        return '';
4260
-    }
4261
-
4262
-
4263
-    /**
4264
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4265
-     * and should be passed HAVING parameters, not WHERE parameters
4266
-     *
4267
-     * @param array $having_params
4268
-     * @return string
4269
-     * @throws EE_Error
4270
-     */
4271
-    private function _construct_having_clause($having_params)
4272
-    {
4273
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4274
-        if ($SQL) {
4275
-            return " HAVING " . $SQL;
4276
-        }
4277
-        return '';
4278
-    }
4279
-
4280
-
4281
-    /**
4282
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4283
-     * Event_Meta.meta_value = 'foo'))"
4284
-     *
4285
-     * @param array  $where_params
4286
-     * @param string $glue joins each sub-clause together. Should really only be " AND " or " OR "...
4287
-     * @return string of SQL
4288
-     * @throws EE_Error
4289
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4290
-     */
4291
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4292
-    {
4293
-        $where_clauses = [];
4294
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4295
-            $query_param =
4296
-                $this->_remove_stars_and_anything_after_from_condition_query_param_key(
4297
-                    $query_param
4298
-                );// str_replace("*",'',$query_param);
4299
-            if (in_array($query_param, $this->_logic_query_param_keys)) {
4300
-                switch ($query_param) {
4301
-                    case 'not':
4302
-                    case 'NOT':
4303
-                        $where_clauses[] = "! ("
4304
-                                           . $this->_construct_condition_clause_recursive(
4305
-                                $op_and_value_or_sub_condition,
4306
-                                $glue
4307
-                            )
4308
-                                           . ")";
4309
-                        break;
4310
-                    case 'and':
4311
-                    case 'AND':
4312
-                        $where_clauses[] = " ("
4313
-                                           . $this->_construct_condition_clause_recursive(
4314
-                                $op_and_value_or_sub_condition,
4315
-                                ' AND '
4316
-                            )
4317
-                                           . ")";
4318
-                        break;
4319
-                    case 'or':
4320
-                    case 'OR':
4321
-                        $where_clauses[] = " ("
4322
-                                           . $this->_construct_condition_clause_recursive(
4323
-                                $op_and_value_or_sub_condition,
4324
-                                ' OR '
4325
-                            )
4326
-                                           . ")";
4327
-                        break;
4328
-                }
4329
-            } else {
4330
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4331
-                // if it's not a normal field, maybe it's a custom selection?
4332
-                if (! $field_obj) {
4333
-                    if ($this->_custom_selections instanceof CustomSelects) {
4334
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4335
-                    } else {
4336
-                        throw new EE_Error(
4337
-                            sprintf(
4338
-                                esc_html__(
4339
-                                    "%s is neither a valid model field name, nor a custom selection",
4340
-                                    "event_espresso"
4341
-                                ),
4342
-                                $query_param
4343
-                            )
4344
-                        );
4345
-                    }
4346
-                }
4347
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4348
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4349
-            }
4350
-        }
4351
-        return $where_clauses ? implode($glue, $where_clauses) : '';
4352
-    }
4353
-
4354
-
4355
-    /**
4356
-     * Takes the input parameter and extract the table name (alias) and column name
4357
-     *
4358
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4359
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4360
-     * @throws EE_Error
4361
-     */
4362
-    private function _deduce_column_name_from_query_param($query_param)
4363
-    {
4364
-        $field = $this->_deduce_field_from_query_param($query_param);
4365
-        if ($field) {
4366
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4367
-                $field->get_model_name(),
4368
-                $query_param
4369
-            );
4370
-            return $table_alias_prefix . $field->get_qualified_column();
4371
-        }
4372
-        if (
4373
-            $this->_custom_selections instanceof CustomSelects
4374
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4375
-        ) {
4376
-            // maybe it's custom selection item?
4377
-            // if so, just use it as the "column name"
4378
-            return $query_param;
4379
-        }
4380
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4381
-            ? implode(',', $this->_custom_selections->columnAliases())
4382
-            : '';
4383
-        throw new EE_Error(
4384
-            sprintf(
4385
-                esc_html__(
4386
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4387
-                    "event_espresso"
4388
-                ),
4389
-                $query_param,
4390
-                $custom_select_aliases
4391
-            )
4392
-        );
4393
-    }
4394
-
4395
-
4396
-    /**
4397
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4398
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4399
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4400
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4401
-     *
4402
-     * @param string $condition_query_param_key
4403
-     * @return string
4404
-     */
4405
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4406
-    {
4407
-        $pos_of_star = strpos($condition_query_param_key, '*');
4408
-        if ($pos_of_star === false) {
4409
-            return $condition_query_param_key;
4410
-        }
4411
-        return substr($condition_query_param_key, 0, $pos_of_star);
4412
-    }
4413
-
4414
-
4415
-    /**
4416
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4417
-     *
4418
-     * @param mixed      array | string    $op_and_value
4419
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4420
-     * @return string
4421
-     * @throws EE_Error
4422
-     */
4423
-    private function _construct_op_and_value($op_and_value, $field_obj)
4424
-    {
4425
-        if (is_array($op_and_value)) {
4426
-            $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4427
-            if (! $operator) {
4428
-                $php_array_like_string = [];
4429
-                foreach ($op_and_value as $key => $value) {
4430
-                    $php_array_like_string[] = "$key=>$value";
4431
-                }
4432
-                throw new EE_Error(
4433
-                    sprintf(
4434
-                        esc_html__(
4435
-                            "You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4436
-                            "event_espresso"
4437
-                        ),
4438
-                        implode(",", $php_array_like_string)
4439
-                    )
4440
-                );
4441
-            }
4442
-            $value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4443
-        } else {
4444
-            $operator = '=';
4445
-            $value    = $op_and_value;
4446
-        }
4447
-        // check to see if the value is actually another field
4448
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4449
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4450
-        }
4451
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4452
-            // in this case, the value should be an array, or at least a comma-separated list
4453
-            // it will need to handle a little differently
4454
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4455
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4456
-            return $operator . SP . $cleaned_value;
4457
-        }
4458
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4459
-            // the value should be an array with count of two.
4460
-            if (count($value) !== 2) {
4461
-                throw new EE_Error(
4462
-                    sprintf(
4463
-                        esc_html__(
4464
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4465
-                            'event_espresso'
4466
-                        ),
4467
-                        "BETWEEN"
4468
-                    )
4469
-                );
4470
-            }
4471
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4472
-            return $operator . SP . $cleaned_value;
4473
-        }
4474
-        if (in_array($operator, $this->valid_null_style_operators())) {
4475
-            if ($value !== null) {
4476
-                throw new EE_Error(
4477
-                    sprintf(
4478
-                        esc_html__(
4479
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4480
-                            "event_espresso"
4481
-                        ),
4482
-                        $value,
4483
-                        $operator
4484
-                    )
4485
-                );
4486
-            }
4487
-            return $operator;
4488
-        }
4489
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4490
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4491
-            // remove other junk. So just treat it as a string.
4492
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4493
-        }
4494
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4495
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4496
-        }
4497
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4498
-            throw new EE_Error(
4499
-                sprintf(
4500
-                    esc_html__(
4501
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4502
-                        'event_espresso'
4503
-                    ),
4504
-                    $operator,
4505
-                    $operator
4506
-                )
4507
-            );
4508
-        }
4509
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4510
-            throw new EE_Error(
4511
-                sprintf(
4512
-                    esc_html__(
4513
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4514
-                        'event_espresso'
4515
-                    ),
4516
-                    $operator,
4517
-                    $operator
4518
-                )
4519
-            );
4520
-        }
4521
-        throw new EE_Error(
4522
-            sprintf(
4523
-                esc_html__(
4524
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4525
-                    "event_espresso"
4526
-                ),
4527
-                http_build_query($op_and_value)
4528
-            )
4529
-        );
4530
-    }
4531
-
4532
-
4533
-    /**
4534
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4535
-     *
4536
-     * @param array                      $values
4537
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4538
-     *                                              '%s'
4539
-     * @return string
4540
-     * @throws EE_Error
4541
-     */
4542
-    public function _construct_between_value($values, $field_obj)
4543
-    {
4544
-        $cleaned_values = [];
4545
-        foreach ($values as $value) {
4546
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4547
-        }
4548
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4549
-    }
4550
-
4551
-
4552
-    /**
4553
-     * Takes an array or a comma-separated list of $values and cleans them
4554
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4555
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4556
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4557
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4558
-     *
4559
-     * @param mixed                      $values    array or comma-separated string
4560
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4561
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4562
-     * @throws EE_Error
4563
-     */
4564
-    public function _construct_in_value($values, $field_obj)
4565
-    {
4566
-        // check if the value is a CSV list
4567
-        if (is_string($values)) {
4568
-            // in which case, turn it into an array
4569
-            $values = explode(",", $values);
4570
-        }
4571
-        $cleaned_values = [];
4572
-        foreach ($values as $value) {
4573
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4574
-        }
4575
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4576
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4577
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4578
-        if (empty($cleaned_values)) {
4579
-            $all_fields       = $this->field_settings();
4580
-            $a_field          = array_shift($all_fields);
4581
-            $main_table       = $this->_get_main_table();
4582
-            $cleaned_values[] = "SELECT "
4583
-                                . $a_field->get_table_column()
4584
-                                . " FROM "
4585
-                                . $main_table->get_table_name()
4586
-                                . " WHERE FALSE";
4587
-        }
4588
-        return "(" . implode(",", $cleaned_values) . ")";
4589
-    }
4590
-
4591
-
4592
-    /**
4593
-     * @param mixed                      $value
4594
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4595
-     * @return false|null|string
4596
-     * @throws EE_Error
4597
-     */
4598
-    private function _wpdb_prepare_using_field($value, $field_obj)
4599
-    {
4600
-        global $wpdb;
4601
-        if ($field_obj instanceof EE_Model_Field_Base) {
4602
-            return $wpdb->prepare(
4603
-                $field_obj->get_wpdb_data_type(),
4604
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4605
-            );
4606
-        } //$field_obj should really just be a data type
4607
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4608
-            throw new EE_Error(
4609
-                sprintf(
4610
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4611
-                    $field_obj,
4612
-                    implode(",", $this->_valid_wpdb_data_types)
4613
-                )
4614
-            );
4615
-        }
4616
-        return $wpdb->prepare($field_obj, $value);
4617
-    }
4618
-
4619
-
4620
-    /**
4621
-     * Takes the input parameter and finds the model field that it indicates.
4622
-     *
4623
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4624
-     * @return EE_Model_Field_Base
4625
-     * @throws EE_Error
4626
-     */
4627
-    protected function _deduce_field_from_query_param($query_param_name)
4628
-    {
4629
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4630
-        // which will help us find the database table and column
4631
-        $query_param_parts = explode(".", $query_param_name);
4632
-        if (empty($query_param_parts)) {
4633
-            throw new EE_Error(
4634
-                sprintf(
4635
-                    esc_html__(
4636
-                        "_extract_column_name is empty when trying to extract column and table name from %s",
4637
-                        'event_espresso'
4638
-                    ),
4639
-                    $query_param_name
4640
-                )
4641
-            );
4642
-        }
4643
-        $number_of_parts       = count($query_param_parts);
4644
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4645
-        if ($number_of_parts === 1) {
4646
-            $field_name = $last_query_param_part;
4647
-            $model_obj  = $this;
4648
-        } else {// $number_of_parts >= 2
4649
-            // the last part is the column name, and there are only 2parts. therefore...
4650
-            $field_name = $last_query_param_part;
4651
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4652
-        }
4653
-        try {
4654
-            return $model_obj->field_settings_for($field_name);
4655
-        } catch (EE_Error $e) {
4656
-            return null;
4657
-        }
4658
-    }
4659
-
4660
-
4661
-    /**
4662
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4663
-     * alias and column which corresponds to it
4664
-     *
4665
-     * @param string $field_name
4666
-     * @return string
4667
-     * @throws EE_Error
4668
-     */
4669
-    public function _get_qualified_column_for_field($field_name)
4670
-    {
4671
-        $all_fields = $this->field_settings();
4672
-        $field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4673
-        if ($field) {
4674
-            return $field->get_qualified_column();
4675
-        }
4676
-        throw new EE_Error(
4677
-            sprintf(
4678
-                esc_html__(
4679
-                    "There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4680
-                    'event_espresso'
4681
-                ),
4682
-                $field_name,
4683
-                get_class($this)
4684
-            )
4685
-        );
4686
-    }
4687
-
4688
-
4689
-    /**
4690
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4691
-     * Example usage:
4692
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4693
-     *      array(),
4694
-     *      ARRAY_A,
4695
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4696
-     *  );
4697
-     * is equivalent to
4698
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4699
-     * and
4700
-     *  EEM_Event::instance()->get_all_wpdb_results(
4701
-     *      array(
4702
-     *          array(
4703
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4704
-     *          ),
4705
-     *          ARRAY_A,
4706
-     *          implode(
4707
-     *              ', ',
4708
-     *              array_merge(
4709
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4710
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4711
-     *              )
4712
-     *          )
4713
-     *      )
4714
-     *  );
4715
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4716
-     *
4717
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4718
-     *                                            and the one whose fields you are selecting for example: when querying
4719
-     *                                            tickets model and selecting fields from the tickets model you would
4720
-     *                                            leave this parameter empty, because no models are needed to join
4721
-     *                                            between the queried model and the selected one. Likewise when
4722
-     *                                            querying the datetime model and selecting fields from the tickets
4723
-     *                                            model, it would also be left empty, because there is a direct
4724
-     *                                            relation from datetimes to tickets, so no model is needed to join
4725
-     *                                            them together. However, when querying from the event model and
4726
-     *                                            selecting fields from the ticket model, you should provide the string
4727
-     *                                            'Datetime', indicating that the event model must first join to the
4728
-     *                                            datetime model in order to find its relation to ticket model.
4729
-     *                                            Also, when querying from the venue model and selecting fields from
4730
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4731
-     *                                            indicating you need to join the venue model to the event model,
4732
-     *                                            to the datetime model, in order to find its relation to the ticket
4733
-     *                                            model. This string is used to deduce the prefix that gets added onto
4734
-     *                                            the models' tables qualified columns
4735
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4736
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4737
-     *                                            qualified column names
4738
-     * @return array|string
4739
-     */
4740
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4741
-    {
4742
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4743
-        $qualified_columns = [];
4744
-        foreach ($this->field_settings() as $field_name => $field) {
4745
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4746
-        }
4747
-        return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4748
-    }
4749
-
4750
-
4751
-    /**
4752
-     * constructs the select use on special limit joins
4753
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4754
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4755
-     * (as that is typically where the limits would be set).
4756
-     *
4757
-     * @param string       $table_alias The table the select is being built for
4758
-     * @param mixed|string $limit       The limit for this select
4759
-     * @return string                The final select join element for the query.
4760
-     * @throws EE_Error
4761
-     */
4762
-    public function _construct_limit_join_select($table_alias, $limit)
4763
-    {
4764
-        $SQL = '';
4765
-        foreach ($this->_tables as $table_obj) {
4766
-            if ($table_obj instanceof EE_Primary_Table) {
4767
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4768
-                    ? $table_obj->get_select_join_limit($limit)
4769
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4770
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4771
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4772
-                    ? $table_obj->get_select_join_limit_join($limit)
4773
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4774
-            }
4775
-        }
4776
-        return $SQL;
4777
-    }
4778
-
4779
-
4780
-    /**
4781
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4782
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4783
-     *
4784
-     * @return string SQL
4785
-     * @throws EE_Error
4786
-     */
4787
-    public function _construct_internal_join()
4788
-    {
4789
-        $SQL = $this->_get_main_table()->get_table_sql();
4790
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4791
-        return $SQL;
4792
-    }
4793
-
4794
-
4795
-    /**
4796
-     * Constructs the SQL for joining all the tables on this model.
4797
-     * Normally $alias should be the primary table's alias, but in cases where
4798
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4799
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4800
-     * alias, this will construct SQL like:
4801
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4802
-     * With $alias being a secondary table's alias, this will construct SQL like:
4803
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4804
-     *
4805
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4806
-     * @return string
4807
-     * @throws EE_Error
4808
-     */
4809
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4810
-    {
4811
-        $SQL               = '';
4812
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4813
-        foreach ($this->_tables as $table_obj) {
4814
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4815
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4816
-                    // so we're joining to this table, meaning the table is already in
4817
-                    // the FROM statement, BUT the primary table isn't. So we want
4818
-                    // to add the inverse join sql
4819
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4820
-                } else {
4821
-                    // just add a regular JOIN to this table from the primary table
4822
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4823
-                }
4824
-            }//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4825
-        }
4826
-        return $SQL;
4827
-    }
4828
-
4829
-
4830
-    /**
4831
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4832
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4833
-     * their data type (eg, '%s', '%d', etc)
4834
-     *
4835
-     * @return array
4836
-     */
4837
-    public function _get_data_types()
4838
-    {
4839
-        $data_types = [];
4840
-        foreach ($this->field_settings() as $field_obj) {
4841
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4842
-            /** @var $field_obj EE_Model_Field_Base */
4843
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4844
-        }
4845
-        return $data_types;
4846
-    }
4847
-
4848
-
4849
-    /**
4850
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4851
-     *
4852
-     * @param string $model_name
4853
-     * @return EEM_Base
4854
-     * @throws EE_Error
4855
-     */
4856
-    public function get_related_model_obj($model_name)
4857
-    {
4858
-        $model_classname = "EEM_" . $model_name;
4859
-        if (! class_exists($model_classname)) {
4860
-            throw new EE_Error(
4861
-                sprintf(
4862
-                    esc_html__(
4863
-                        "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4864
-                        'event_espresso'
4865
-                    ),
4866
-                    $model_name,
4867
-                    $model_classname
4868
-                )
4869
-            );
4870
-        }
4871
-        return call_user_func($model_classname . "::instance");
4872
-    }
4873
-
4874
-
4875
-    /**
4876
-     * Returns the array of EE_ModelRelations for this model.
4877
-     *
4878
-     * @return EE_Model_Relation_Base[]
4879
-     */
4880
-    public function relation_settings()
4881
-    {
4882
-        return $this->_model_relations;
4883
-    }
4884
-
4885
-
4886
-    /**
4887
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4888
-     * because without THOSE models, this model probably doesn't have much purpose.
4889
-     * (Eg, without an event, datetimes have little purpose.)
4890
-     *
4891
-     * @return EE_Belongs_To_Relation[]
4892
-     */
4893
-    public function belongs_to_relations()
4894
-    {
4895
-        $belongs_to_relations = [];
4896
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4897
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
4898
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4899
-            }
4900
-        }
4901
-        return $belongs_to_relations;
4902
-    }
4903
-
4904
-
4905
-    /**
4906
-     * Returns the specified EE_Model_Relation, or throws an exception
4907
-     *
4908
-     * @param string $relation_name name of relation, key in $this->_relatedModels
4909
-     * @return EE_Model_Relation_Base
4910
-     * @throws EE_Error
4911
-     */
4912
-    public function related_settings_for($relation_name)
4913
-    {
4914
-        $relatedModels = $this->relation_settings();
4915
-        if (! array_key_exists($relation_name, $relatedModels)) {
4916
-            throw new EE_Error(
4917
-                sprintf(
4918
-                    esc_html__(
4919
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4920
-                        'event_espresso'
4921
-                    ),
4922
-                    $relation_name,
4923
-                    $this->_get_class_name(),
4924
-                    implode(', ', array_keys($relatedModels))
4925
-                )
4926
-            );
4927
-        }
4928
-        return $relatedModels[ $relation_name ];
4929
-    }
4930
-
4931
-
4932
-    /**
4933
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4934
-     * fields
4935
-     *
4936
-     * @param string  $fieldName
4937
-     * @param boolean $include_db_only_fields
4938
-     * @return EE_Model_Field_Base
4939
-     * @throws EE_Error
4940
-     */
4941
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
4942
-    {
4943
-        $fieldSettings = $this->field_settings($include_db_only_fields);
4944
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4945
-            throw new EE_Error(
4946
-                sprintf(
4947
-                    esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4948
-                    $fieldName,
4949
-                    get_class($this)
4950
-                )
4951
-            );
4952
-        }
4953
-        return $fieldSettings[ $fieldName ];
4954
-    }
4955
-
4956
-
4957
-    /**
4958
-     * Checks if this field exists on this model
4959
-     *
4960
-     * @param string $fieldName a key in the model's _field_settings array
4961
-     * @return boolean
4962
-     */
4963
-    public function has_field($fieldName)
4964
-    {
4965
-        $fieldSettings = $this->field_settings(true);
4966
-        if (isset($fieldSettings[ $fieldName ])) {
4967
-            return true;
4968
-        }
4969
-        return false;
4970
-    }
4971
-
4972
-
4973
-    /**
4974
-     * Returns whether or not this model has a relation to the specified model
4975
-     *
4976
-     * @param string $relation_name possibly one of the keys in the relation_settings array
4977
-     * @return boolean
4978
-     */
4979
-    public function has_relation($relation_name)
4980
-    {
4981
-        $relations = $this->relation_settings();
4982
-        if (isset($relations[ $relation_name ])) {
4983
-            return true;
4984
-        }
4985
-        return false;
4986
-    }
4987
-
4988
-
4989
-    /**
4990
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4991
-     * Eg, on EE_Answer that would be ANS_ID field object
4992
-     *
4993
-     * @param $field_obj
4994
-     * @return boolean
4995
-     */
4996
-    public function is_primary_key_field($field_obj)
4997
-    {
4998
-        return $field_obj instanceof EE_Primary_Key_Field_Base;
4999
-    }
5000
-
5001
-
5002
-    /**
5003
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5004
-     * Eg, on EE_Answer that would be ANS_ID field object
5005
-     *
5006
-     * @return EE_Model_Field_Base
5007
-     * @throws EE_Error
5008
-     */
5009
-    public function get_primary_key_field()
5010
-    {
5011
-        if ($this->_primary_key_field === null) {
5012
-            foreach ($this->field_settings(true) as $field_obj) {
5013
-                if ($this->is_primary_key_field($field_obj)) {
5014
-                    $this->_primary_key_field = $field_obj;
5015
-                    break;
5016
-                }
5017
-            }
5018
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5019
-                throw new EE_Error(
5020
-                    sprintf(
5021
-                        esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5022
-                        get_class($this)
5023
-                    )
5024
-                );
5025
-            }
5026
-        }
5027
-        return $this->_primary_key_field;
5028
-    }
5029
-
5030
-
5031
-    /**
5032
-     * Returns whether or not not there is a primary key on this model.
5033
-     * Internally does some caching.
5034
-     *
5035
-     * @return boolean
5036
-     */
5037
-    public function has_primary_key_field()
5038
-    {
5039
-        if ($this->_has_primary_key_field === null) {
5040
-            try {
5041
-                $this->get_primary_key_field();
5042
-                $this->_has_primary_key_field = true;
5043
-            } catch (EE_Error $e) {
5044
-                $this->_has_primary_key_field = false;
5045
-            }
5046
-        }
5047
-        return $this->_has_primary_key_field;
5048
-    }
5049
-
5050
-
5051
-    /**
5052
-     * Finds the first field of type $field_class_name.
5053
-     *
5054
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5055
-     *                                 EE_Foreign_Key_Field, etc
5056
-     * @return EE_Model_Field_Base or null if none is found
5057
-     */
5058
-    public function get_a_field_of_type($field_class_name)
5059
-    {
5060
-        foreach ($this->field_settings() as $field) {
5061
-            if ($field instanceof $field_class_name) {
5062
-                return $field;
5063
-            }
5064
-        }
5065
-        return null;
5066
-    }
5067
-
5068
-
5069
-    /**
5070
-     * Gets a foreign key field pointing to model.
5071
-     *
5072
-     * @param string $model_name eg Event, Registration, not EEM_Event
5073
-     * @return EE_Foreign_Key_Field_Base
5074
-     * @throws EE_Error
5075
-     */
5076
-    public function get_foreign_key_to($model_name)
5077
-    {
5078
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5079
-            foreach ($this->field_settings() as $field) {
5080
-                if (
5081
-                    $field instanceof EE_Foreign_Key_Field_Base
5082
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5083
-                ) {
5084
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5085
-                    break;
5086
-                }
5087
-            }
5088
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5089
-                throw new EE_Error(
5090
-                    sprintf(
5091
-                        esc_html__(
5092
-                            "There is no foreign key field pointing to model %s on model %s",
5093
-                            'event_espresso'
5094
-                        ),
5095
-                        $model_name,
5096
-                        get_class($this)
5097
-                    )
5098
-                );
5099
-            }
5100
-        }
5101
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5102
-    }
5103
-
5104
-
5105
-    /**
5106
-     * Gets the table name (including $wpdb->prefix) for the table alias
5107
-     *
5108
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5109
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5110
-     *                            Either one works
5111
-     * @return string
5112
-     */
5113
-    public function get_table_for_alias($table_alias)
5114
-    {
5115
-        $table_alias_sans_model_relation_chain_prefix =
5116
-            EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5117
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5118
-    }
5119
-
5120
-
5121
-    /**
5122
-     * Returns a flat array of all field son this model, instead of organizing them
5123
-     * by table_alias as they are in the constructor.
5124
-     *
5125
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5126
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5127
-     */
5128
-    public function field_settings($include_db_only_fields = false)
5129
-    {
5130
-        if ($include_db_only_fields) {
5131
-            if ($this->_cached_fields === null) {
5132
-                $this->_cached_fields = [];
5133
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5134
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5135
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5136
-                    }
5137
-                }
5138
-            }
5139
-            return $this->_cached_fields;
5140
-        }
5141
-        if ($this->_cached_fields_non_db_only === null) {
5142
-            $this->_cached_fields_non_db_only = [];
5143
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5144
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5145
-                    /** @var $field_obj EE_Model_Field_Base */
5146
-                    if (! $field_obj->is_db_only_field()) {
5147
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5148
-                    }
5149
-                }
5150
-            }
5151
-        }
5152
-        return $this->_cached_fields_non_db_only;
5153
-    }
5154
-
5155
-
5156
-    /**
5157
-     *        cycle though array of attendees and create objects out of each item
5158
-     *
5159
-     * @access        private
5160
-     * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5161
-     * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5162
-     *                           numerically indexed)
5163
-     * @throws EE_Error
5164
-     * @throws ReflectionException
5165
-     */
5166
-    protected function _create_objects($rows = [])
5167
-    {
5168
-        $array_of_objects = [];
5169
-        if (empty($rows)) {
5170
-            return [];
5171
-        }
5172
-        $count_if_model_has_no_primary_key = 0;
5173
-        $has_primary_key                   = $this->has_primary_key_field();
5174
-        $primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5175
-        foreach ((array)$rows as $row) {
5176
-            if (empty($row)) {
5177
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5178
-                return [];
5179
-            }
5180
-            // check if we've already set this object in the results array,
5181
-            // in which case there's no need to process it further (again)
5182
-            if ($has_primary_key) {
5183
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5184
-                    $row,
5185
-                    $primary_key_field->get_qualified_column(),
5186
-                    $primary_key_field->get_table_column()
5187
-                );
5188
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5189
-                    continue;
5190
-                }
5191
-            }
5192
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5193
-            if (! $classInstance) {
5194
-                throw new EE_Error(
5195
-                    sprintf(
5196
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5197
-                        $this->get_this_model_name(),
5198
-                        http_build_query($row)
5199
-                    )
5200
-                );
5201
-            }
5202
-            // set the timezone on the instantiated objects
5203
-            $classInstance->set_timezone($this->_timezone);
5204
-            // make sure if there is any timezone setting present that we set the timezone for the object
5205
-            $key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5206
-            $array_of_objects[ $key ] = $classInstance;
5207
-            // also, for all the relations of type BelongsTo, see if we can cache
5208
-            // those related models
5209
-            // (we could do this for other relations too, but if there are conditions
5210
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5211
-            // so it requires a little more thought than just caching them immediately...)
5212
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5213
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5214
-                    // check if this model's INFO is present. If so, cache it on the model
5215
-                    $other_model           = $relation_obj->get_other_model();
5216
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5217
-                    // if we managed to make a model object from the results, cache it on the main model object
5218
-                    if ($other_model_obj_maybe) {
5219
-                        // set timezone on these other model objects if they are present
5220
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5221
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5222
-                    }
5223
-                }
5224
-            }
5225
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5226
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5227
-            // the field in the CustomSelects object
5228
-            if ($this->_custom_selections instanceof CustomSelects) {
5229
-                $classInstance->setCustomSelectsValues(
5230
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5231
-                );
5232
-            }
5233
-        }
5234
-        return $array_of_objects;
5235
-    }
5236
-
5237
-
5238
-    /**
5239
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5240
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5241
-     *
5242
-     * @param array $db_results_row
5243
-     * @return array
5244
-     */
5245
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5246
-    {
5247
-        $results = [];
5248
-        if ($this->_custom_selections instanceof CustomSelects) {
5249
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5250
-                if (isset($db_results_row[ $alias ])) {
5251
-                    $results[ $alias ] = $this->convertValueToDataType(
5252
-                        $db_results_row[ $alias ],
5253
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5254
-                    );
5255
-                }
5256
-            }
5257
-        }
5258
-        return $results;
5259
-    }
5260
-
5261
-
5262
-    /**
5263
-     * This will set the value for the given alias
5264
-     *
5265
-     * @param string $value
5266
-     * @param string $datatype (one of %d, %s, %f)
5267
-     * @return int|string|float (int for %d, string for %s, float for %f)
5268
-     */
5269
-    protected function convertValueToDataType($value, $datatype)
5270
-    {
5271
-        switch ($datatype) {
5272
-            case '%f':
5273
-                return (float)$value;
5274
-            case '%d':
5275
-                return (int)$value;
5276
-            default:
5277
-                return (string)$value;
5278
-        }
5279
-    }
5280
-
5281
-
5282
-    /**
5283
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5284
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5285
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5286
-     * object (as set in the model_field!).
5287
-     *
5288
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5289
-     * @throws EE_Error
5290
-     * @throws ReflectionException
5291
-     */
5292
-    public function create_default_object()
5293
-    {
5294
-        $this_model_fields_and_values = [];
5295
-        // setup the row using default values;
5296
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5297
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5298
-        }
5299
-        $className = $this->_get_class_name();
5300
-        return EE_Registry::instance()->load_class(
5301
-            $className,
5302
-            [$this_model_fields_and_values],
5303
-            false,
5304
-            false
5305
-        );
5306
-    }
5307
-
5308
-
5309
-    /**
5310
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5311
-     *                             or an stdClass where each property is the name of a column,
5312
-     * @return EE_Base_Class
5313
-     * @throws EE_Error
5314
-     * @throws ReflectionException
5315
-     */
5316
-    public function instantiate_class_from_array_or_object($cols_n_values)
5317
-    {
5318
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5319
-            $cols_n_values = get_object_vars($cols_n_values);
5320
-        }
5321
-        $primary_key = null;
5322
-        // make sure the array only has keys that are fields/columns on this model
5323
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5324
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5325
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5326
-        }
5327
-        $className = $this->_get_class_name();
5328
-        // check we actually found results that we can use to build our model object
5329
-        // if not, return null
5330
-        if ($this->has_primary_key_field()) {
5331
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5332
-                return null;
5333
-            }
5334
-        } elseif ($this->unique_indexes()) {
5335
-            $first_column = reset($this_model_fields_n_values);
5336
-            if (empty($first_column)) {
5337
-                return null;
5338
-            }
5339
-        }
5340
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5341
-        if ($primary_key) {
5342
-            $classInstance = $this->get_from_entity_map($primary_key);
5343
-            if (! $classInstance) {
5344
-                $classInstance = EE_Registry::instance()->load_class(
5345
-                    $className,
5346
-                    [$this_model_fields_n_values, $this->_timezone],
5347
-                    true,
5348
-                    false
5349
-                );
5350
-                // add this new object to the entity map
5351
-                $classInstance = $this->add_to_entity_map($classInstance);
5352
-            }
5353
-        } else {
5354
-            $classInstance = EE_Registry::instance()->load_class(
5355
-                $className,
5356
-                [$this_model_fields_n_values, $this->_timezone],
5357
-                true,
5358
-                false
5359
-            );
5360
-        }
5361
-        return $classInstance;
5362
-    }
5363
-
5364
-
5365
-    /**
5366
-     * Gets the model object from the  entity map if it exists
5367
-     *
5368
-     * @param int|string $id the ID of the model object
5369
-     * @return EE_Base_Class
5370
-     */
5371
-    public function get_from_entity_map($id)
5372
-    {
5373
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5374
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5375
-    }
5376
-
5377
-
5378
-    /**
5379
-     * add_to_entity_map
5380
-     * Adds the object to the model's entity mappings
5381
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5382
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5383
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5384
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5385
-     *        then this method should be called immediately after the update query
5386
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5387
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5388
-     *
5389
-     * @param EE_Base_Class $object
5390
-     * @return EE_Base_Class
5391
-     * @throws EE_Error
5392
-     * @throws ReflectionException
5393
-     */
5394
-    public function add_to_entity_map(EE_Base_Class $object)
5395
-    {
5396
-        $className = $this->_get_class_name();
5397
-        if (! $object instanceof $className) {
5398
-            throw new EE_Error(
5399
-                sprintf(
5400
-                    esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5401
-                    is_object($object) ? get_class($object) : $object,
5402
-                    $className
5403
-                )
5404
-            );
5405
-        }
5406
-        if (! $object->ID()) {
5407
-            throw new EE_Error(
5408
-                sprintf(
5409
-                    esc_html__(
5410
-                        "You tried storing a model object with NO ID in the %s entity mapper.",
5411
-                        "event_espresso"
5412
-                    ),
5413
-                    get_class($this)
5414
-                )
5415
-            );
5416
-        }
5417
-        // double check it's not already there
5418
-        $classInstance = $this->get_from_entity_map($object->ID());
5419
-        if ($classInstance) {
5420
-            return $classInstance;
5421
-        }
5422
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5423
-        return $object;
5424
-    }
5425
-
5426
-
5427
-    /**
5428
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5429
-     * if no identifier is provided, then the entire entity map is emptied
5430
-     *
5431
-     * @param int|string $id the ID of the model object
5432
-     * @return boolean
5433
-     */
5434
-    public function clear_entity_map($id = null)
5435
-    {
5436
-        if (empty($id)) {
5437
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5438
-            return true;
5439
-        }
5440
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5441
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5442
-            return true;
5443
-        }
5444
-        return false;
5445
-    }
5446
-
5447
-
5448
-    /**
5449
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5450
-     * Given an array where keys are column (or column alias) names and values,
5451
-     * returns an array of their corresponding field names and database values
5452
-     *
5453
-     * @param array $cols_n_values
5454
-     * @return array
5455
-     */
5456
-    public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5457
-    {
5458
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5459
-    }
5460
-
5461
-
5462
-    /**
5463
-     * _deduce_fields_n_values_from_cols_n_values
5464
-     * Given an array where keys are column (or column alias) names and values,
5465
-     * returns an array of their corresponding field names and database values
5466
-     *
5467
-     * @param array $cols_n_values
5468
-     * @return array
5469
-     */
5470
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5471
-    {
5472
-        $this_model_fields_n_values = [];
5473
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5474
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5475
-                $cols_n_values,
5476
-                $table_obj->get_fully_qualified_pk_column(),
5477
-                $table_obj->get_pk_column()
5478
-            );
5479
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5480
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5481
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5482
-                    if (! $field_obj->is_db_only_field()) {
5483
-                        // prepare field as if its coming from db
5484
-                        $prepared_value                            =
5485
-                            $field_obj->prepare_for_set($field_obj->get_default_value());
5486
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5487
-                    }
5488
-                }
5489
-            } else {
5490
-                // the table's rows existed. Use their values
5491
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5492
-                    if (! $field_obj->is_db_only_field()) {
5493
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5494
-                            $cols_n_values,
5495
-                            $field_obj->get_qualified_column(),
5496
-                            $field_obj->get_table_column()
5497
-                        );
5498
-                    }
5499
-                }
5500
-            }
5501
-        }
5502
-        return $this_model_fields_n_values;
5503
-    }
5504
-
5505
-
5506
-    /**
5507
-     * @param array  $cols_n_values
5508
-     * @param string $qualified_column
5509
-     * @param string $regular_column
5510
-     * @return null
5511
-     */
5512
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5513
-    {
5514
-        $value = null;
5515
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5516
-        // does the field on the model relate to this column retrieved from the db?
5517
-        // or is it a db-only field? (not relating to the model)
5518
-        if (isset($cols_n_values[ $qualified_column ])) {
5519
-            $value = $cols_n_values[ $qualified_column ];
5520
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5521
-            $value = $cols_n_values[ $regular_column ];
5522
-        } elseif (! empty($this->foreign_key_aliases)) {
5523
-            // no PK?  ok check if there is a foreign key alias set for this table
5524
-            // then check if that alias exists in the incoming data
5525
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5526
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5527
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5528
-                    $value = $cols_n_values[ $FK_alias ];
5529
-                    break;
5530
-                }
5531
-            }
5532
-        }
5533
-        return $value;
5534
-    }
5535
-
5536
-
5537
-    /**
5538
-     * refresh_entity_map_from_db
5539
-     * Makes sure the model object in the entity map at $id assumes the values
5540
-     * of the database (opposite of EE_base_Class::save())
5541
-     *
5542
-     * @param int|string $id
5543
-     * @return EE_Base_Class
5544
-     * @throws EE_Error
5545
-     * @throws ReflectionException
5546
-     */
5547
-    public function refresh_entity_map_from_db($id)
5548
-    {
5549
-        $obj_in_map = $this->get_from_entity_map($id);
5550
-        if ($obj_in_map) {
5551
-            $wpdb_results = $this->_get_all_wpdb_results(
5552
-                [[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5553
-            );
5554
-            if ($wpdb_results && is_array($wpdb_results)) {
5555
-                $one_row = reset($wpdb_results);
5556
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5557
-                    $obj_in_map->set_from_db($field_name, $db_value);
5558
-                }
5559
-                // clear the cache of related model objects
5560
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5561
-                    $obj_in_map->clear_cache($relation_name, null, true);
5562
-                }
5563
-            }
5564
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5565
-            return $obj_in_map;
5566
-        }
5567
-        return $this->get_one_by_ID($id);
5568
-    }
5569
-
5570
-
5571
-    /**
5572
-     * refresh_entity_map_with
5573
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5574
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5575
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5576
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5577
-     *
5578
-     * @param int|string    $id
5579
-     * @param EE_Base_Class $replacing_model_obj
5580
-     * @return EE_Base_Class
5581
-     * @throws EE_Error
5582
-     * @throws ReflectionException
5583
-     */
5584
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5585
-    {
5586
-        $obj_in_map = $this->get_from_entity_map($id);
5587
-        if ($obj_in_map) {
5588
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5589
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5590
-                    $obj_in_map->set($field_name, $value);
5591
-                }
5592
-                // make the model object in the entity map's cache match the $replacing_model_obj
5593
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5594
-                    $obj_in_map->clear_cache($relation_name, null, true);
5595
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5596
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5597
-                    }
5598
-                }
5599
-            }
5600
-            return $obj_in_map;
5601
-        }
5602
-        $this->add_to_entity_map($replacing_model_obj);
5603
-        return $replacing_model_obj;
5604
-    }
5605
-
5606
-
5607
-    /**
5608
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5609
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5610
-     * require_once($this->_getClassName().".class.php");
5611
-     *
5612
-     * @return string
5613
-     */
5614
-    private function _get_class_name()
5615
-    {
5616
-        return "EE_" . $this->get_this_model_name();
5617
-    }
5618
-
5619
-
5620
-    /**
5621
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5622
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5623
-     * it would be 'Events'.
5624
-     *
5625
-     * @param int $quantity
5626
-     * @return string
5627
-     */
5628
-    public function item_name($quantity = 1)
5629
-    {
5630
-        return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5631
-    }
5632
-
5633
-
5634
-    /**
5635
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5636
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5637
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5638
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5639
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5640
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5641
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5642
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5643
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5644
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5645
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5646
-     *        return $previousReturnValue.$returnString;
5647
-     * }
5648
-     * require('EEM_Answer.model.php');
5649
-     * $answer=EEM_Answer::instance();
5650
-     * echo $answer->my_callback('monkeys',100);
5651
-     * //will output "you called my_callback! and passed args:monkeys,100"
5652
-     *
5653
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5654
-     * @param array  $args       array of original arguments passed to the function
5655
-     * @return mixed whatever the plugin which calls add_filter decides
5656
-     * @throws EE_Error
5657
-     */
5658
-    public function __call($methodName, $args)
5659
-    {
5660
-        $className = get_class($this);
5661
-        $tagName   = "FHEE__{$className}__{$methodName}";
5662
-        if (! has_filter($tagName)) {
5663
-            throw new EE_Error(
5664
-                sprintf(
5665
-                    esc_html__(
5666
-                        'Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5667
-                        'event_espresso'
5668
-                    ),
5669
-                    $methodName,
5670
-                    $className,
5671
-                    $tagName,
5672
-                    '<br />'
5673
-                )
5674
-            );
5675
-        }
5676
-        return apply_filters($tagName, null, $this, $args);
5677
-    }
5678
-
5679
-
5680
-    /**
5681
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5682
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5683
-     *
5684
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5685
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5686
-     *                                                       the object's class name
5687
-     *                                                       or object's ID
5688
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5689
-     *                                                       exists in the database. If it does not, we add it
5690
-     * @return EE_Base_Class
5691
-     * @throws EE_Error
5692
-     * @throws ReflectionException
5693
-     */
5694
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5695
-    {
5696
-        $className = $this->_get_class_name();
5697
-        if ($base_class_obj_or_id instanceof $className) {
5698
-            $model_object = $base_class_obj_or_id;
5699
-        } else {
5700
-            $primary_key_field = $this->get_primary_key_field();
5701
-            if (
5702
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5703
-                && (
5704
-                    is_int($base_class_obj_or_id)
5705
-                    || is_string($base_class_obj_or_id)
5706
-                )
5707
-            ) {
5708
-                // assume it's an ID.
5709
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5710
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5711
-            } elseif (
5712
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5713
-                && is_string($base_class_obj_or_id)
5714
-            ) {
5715
-                // assume its a string representation of the object
5716
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5717
-            } else {
5718
-                throw new EE_Error(
5719
-                    sprintf(
5720
-                        esc_html__(
5721
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5722
-                            'event_espresso'
5723
-                        ),
5724
-                        $base_class_obj_or_id,
5725
-                        $this->_get_class_name(),
5726
-                        print_r($base_class_obj_or_id, true)
5727
-                    )
5728
-                );
5729
-            }
5730
-        }
5731
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5732
-            $model_object->save();
5733
-        }
5734
-        return $model_object;
5735
-    }
5736
-
5737
-
5738
-    /**
5739
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5740
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5741
-     * returns it ID.
5742
-     *
5743
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5744
-     * @return int|string depending on the type of this model object's ID
5745
-     * @throws EE_Error
5746
-     * @throws ReflectionException
5747
-     */
5748
-    public function ensure_is_ID($base_class_obj_or_id)
5749
-    {
5750
-        $className = $this->_get_class_name();
5751
-        if ($base_class_obj_or_id instanceof $className) {
5752
-            $id = $base_class_obj_or_id->ID();
5753
-        } elseif (is_int($base_class_obj_or_id)) {
5754
-            // assume it's an ID
5755
-            $id = $base_class_obj_or_id;
5756
-        } elseif (is_string($base_class_obj_or_id)) {
5757
-            // assume its a string representation of the object
5758
-            $id = $base_class_obj_or_id;
5759
-        } else {
5760
-            throw new EE_Error(
5761
-                sprintf(
5762
-                    esc_html__(
5763
-                        "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5764
-                        'event_espresso'
5765
-                    ),
5766
-                    $base_class_obj_or_id,
5767
-                    $this->_get_class_name(),
5768
-                    print_r($base_class_obj_or_id, true)
5769
-                )
5770
-            );
5771
-        }
5772
-        return $id;
5773
-    }
5774
-
5775
-
5776
-    /**
5777
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5778
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5779
-     * been sanitized and converted into the appropriate domain.
5780
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5781
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5782
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5783
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5784
-     * $EVT = EEM_Event::instance(); $old_setting =
5785
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5786
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5787
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5788
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5789
-     *
5790
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5791
-     * @return void
5792
-     */
5793
-    public function assume_values_already_prepared_by_model_object(
5794
-        $values_already_prepared = self::not_prepared_by_model_object
5795
-    ) {
5796
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5797
-    }
5798
-
5799
-
5800
-    /**
5801
-     * Read comments for assume_values_already_prepared_by_model_object()
5802
-     *
5803
-     * @return int
5804
-     */
5805
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5806
-    {
5807
-        return $this->_values_already_prepared_by_model_object;
5808
-    }
5809
-
5810
-
5811
-    /**
5812
-     * Gets all the indexes on this model
5813
-     *
5814
-     * @return EE_Index[]
5815
-     */
5816
-    public function indexes()
5817
-    {
5818
-        return $this->_indexes;
5819
-    }
5820
-
5821
-
5822
-    /**
5823
-     * Gets all the Unique Indexes on this model
5824
-     *
5825
-     * @return EE_Unique_Index[]
5826
-     */
5827
-    public function unique_indexes()
5828
-    {
5829
-        $unique_indexes = [];
5830
-        foreach ($this->_indexes as $name => $index) {
5831
-            if ($index instanceof EE_Unique_Index) {
5832
-                $unique_indexes [ $name ] = $index;
5833
-            }
5834
-        }
5835
-        return $unique_indexes;
5836
-    }
5837
-
5838
-
5839
-    /**
5840
-     * Gets all the fields which, when combined, make the primary key.
5841
-     * This is usually just an array with 1 element (the primary key), but in cases
5842
-     * where there is no primary key, it's a combination of fields as defined
5843
-     * on a primary index
5844
-     *
5845
-     * @return EE_Model_Field_Base[] indexed by the field's name
5846
-     * @throws EE_Error
5847
-     */
5848
-    public function get_combined_primary_key_fields()
5849
-    {
5850
-        foreach ($this->indexes() as $index) {
5851
-            if ($index instanceof EE_Primary_Key_Index) {
5852
-                return $index->fields();
5853
-            }
5854
-        }
5855
-        return [$this->primary_key_name() => $this->get_primary_key_field()];
5856
-    }
5857
-
5858
-
5859
-    /**
5860
-     * Used to build a primary key string (when the model has no primary key),
5861
-     * which can be used a unique string to identify this model object.
5862
-     *
5863
-     * @param array $fields_n_values keys are field names, values are their values.
5864
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5865
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5866
-     *                               before passing it to this function (that will convert it from columns-n-values
5867
-     *                               to field-names-n-values).
5868
-     * @return string
5869
-     * @throws EE_Error
5870
-     */
5871
-    public function get_index_primary_key_string($fields_n_values)
5872
-    {
5873
-        $cols_n_values_for_primary_key_index = array_intersect_key(
5874
-            $fields_n_values,
5875
-            $this->get_combined_primary_key_fields()
5876
-        );
5877
-        return http_build_query($cols_n_values_for_primary_key_index);
5878
-    }
5879
-
5880
-
5881
-    /**
5882
-     * Gets the field values from the primary key string
5883
-     *
5884
-     * @param string $index_primary_key_string
5885
-     * @return null|array
5886
-     * @throws EE_Error
5887
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5888
-     */
5889
-    public function parse_index_primary_key_string($index_primary_key_string)
5890
-    {
5891
-        $key_fields = $this->get_combined_primary_key_fields();
5892
-        // check all of them are in the $id
5893
-        $key_values_in_combined_pk = [];
5894
-        parse_str($index_primary_key_string, $key_values_in_combined_pk);
5895
-        foreach ($key_fields as $key_field_name => $field_obj) {
5896
-            if (! isset($key_values_in_combined_pk[ $key_field_name ])) {
5897
-                return null;
5898
-            }
5899
-        }
5900
-        return $key_values_in_combined_pk;
5901
-    }
5902
-
5903
-
5904
-    /**
5905
-     * verifies that an array of key-value pairs for model fields has a key
5906
-     * for each field comprising the primary key index
5907
-     *
5908
-     * @param array $key_values
5909
-     * @return boolean
5910
-     * @throws EE_Error
5911
-     */
5912
-    public function has_all_combined_primary_key_fields($key_values)
5913
-    {
5914
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5915
-        foreach ($keys_it_should_have as $key) {
5916
-            if (! isset($key_values[ $key ])) {
5917
-                return false;
5918
-            }
5919
-        }
5920
-        return true;
5921
-    }
5922
-
5923
-
5924
-    /**
5925
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5926
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
5927
-     *
5928
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5929
-     * @param array               $query_params                     see github link below for more info
5930
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5931
-     * @throws EE_Error
5932
-     * @throws ReflectionException
5933
-     * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5934
-     *                                                              indexed)
5935
-     */
5936
-    public function get_all_copies($model_object_or_attributes_array, $query_params = [])
5937
-    {
5938
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5939
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
5940
-        } elseif (is_array($model_object_or_attributes_array)) {
5941
-            $attributes_array = $model_object_or_attributes_array;
5942
-        } else {
5943
-            throw new EE_Error(
5944
-                sprintf(
5945
-                    esc_html__(
5946
-                        "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5947
-                        "event_espresso"
5948
-                    ),
5949
-                    $model_object_or_attributes_array
5950
-                )
5951
-            );
5952
-        }
5953
-        // even copies obviously won't have the same ID, so remove the primary key
5954
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
5955
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5956
-            unset($attributes_array[ $this->primary_key_name() ]);
5957
-        }
5958
-        if (isset($query_params[0])) {
5959
-            $query_params[0] = array_merge($attributes_array, $query_params);
5960
-        } else {
5961
-            $query_params[0] = $attributes_array;
5962
-        }
5963
-        return $this->get_all($query_params);
5964
-    }
5965
-
5966
-
5967
-    /**
5968
-     * Gets the first copy we find. See get_all_copies for more details
5969
-     *
5970
-     * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
5971
-     * @param array $query_params
5972
-     * @return EE_Base_Class
5973
-     * @throws EE_Error
5974
-     * @throws ReflectionException
5975
-     */
5976
-    public function get_one_copy($model_object_or_attributes_array, $query_params = [])
5977
-    {
5978
-        if (! is_array($query_params)) {
5979
-            EE_Error::doing_it_wrong(
5980
-                'EEM_Base::get_one_copy',
5981
-                sprintf(
5982
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5983
-                    gettype($query_params)
5984
-                ),
5985
-                '4.6.0'
5986
-            );
5987
-            $query_params = [];
5988
-        }
5989
-        $query_params['limit'] = 1;
5990
-        $copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5991
-        if (is_array($copies)) {
5992
-            return array_shift($copies);
5993
-        }
5994
-        return null;
5995
-    }
5996
-
5997
-
5998
-    /**
5999
-     * Updates the item with the specified id. Ignores default query parameters because
6000
-     * we have specified the ID, and its assumed we KNOW what we're doing
6001
-     *
6002
-     * @param array      $fields_n_values keys are field names, values are their new values
6003
-     * @param int|string $id              the value of the primary key to update
6004
-     * @return int number of rows updated
6005
-     * @throws EE_Error
6006
-     * @throws ReflectionException
6007
-     */
6008
-    public function update_by_ID($fields_n_values, $id)
6009
-    {
6010
-        $query_params = [
6011
-            0                          => [$this->get_primary_key_field()->get_name() => $id],
6012
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6013
-        ];
6014
-        return $this->update($fields_n_values, $query_params);
6015
-    }
6016
-
6017
-
6018
-    /**
6019
-     * Changes an operator which was supplied to the models into one usable in SQL
6020
-     *
6021
-     * @param string $operator_supplied
6022
-     * @return string an operator which can be used in SQL
6023
-     * @throws EE_Error
6024
-     */
6025
-    private function _prepare_operator_for_sql($operator_supplied)
6026
-    {
6027
-        $sql_operator =
6028
-            isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6029
-                : null;
6030
-        if ($sql_operator) {
6031
-            return $sql_operator;
6032
-        }
6033
-        throw new EE_Error(
6034
-            sprintf(
6035
-                esc_html__(
6036
-                    "The operator '%s' is not in the list of valid operators: %s",
6037
-                    "event_espresso"
6038
-                ),
6039
-                $operator_supplied,
6040
-                implode(",", array_keys($this->_valid_operators))
6041
-            )
6042
-        );
6043
-    }
6044
-
6045
-
6046
-    /**
6047
-     * Gets the valid operators
6048
-     *
6049
-     * @return array keys are accepted strings, values are the SQL they are converted to
6050
-     */
6051
-    public function valid_operators()
6052
-    {
6053
-        return $this->_valid_operators;
6054
-    }
6055
-
6056
-
6057
-    /**
6058
-     * Gets the between-style operators (take 2 arguments).
6059
-     *
6060
-     * @return array keys are accepted strings, values are the SQL they are converted to
6061
-     */
6062
-    public function valid_between_style_operators()
6063
-    {
6064
-        return array_intersect(
6065
-            $this->valid_operators(),
6066
-            $this->_between_style_operators
6067
-        );
6068
-    }
6069
-
6070
-
6071
-    /**
6072
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6073
-     *
6074
-     * @return array keys are accepted strings, values are the SQL they are converted to
6075
-     */
6076
-    public function valid_like_style_operators()
6077
-    {
6078
-        return array_intersect(
6079
-            $this->valid_operators(),
6080
-            $this->_like_style_operators
6081
-        );
6082
-    }
6083
-
6084
-
6085
-    /**
6086
-     * Gets the "in"-style operators
6087
-     *
6088
-     * @return array keys are accepted strings, values are the SQL they are converted to
6089
-     */
6090
-    public function valid_in_style_operators()
6091
-    {
6092
-        return array_intersect(
6093
-            $this->valid_operators(),
6094
-            $this->_in_style_operators
6095
-        );
6096
-    }
6097
-
6098
-
6099
-    /**
6100
-     * Gets the "null"-style operators (accept no arguments)
6101
-     *
6102
-     * @return array keys are accepted strings, values are the SQL they are converted to
6103
-     */
6104
-    public function valid_null_style_operators()
6105
-    {
6106
-        return array_intersect(
6107
-            $this->valid_operators(),
6108
-            $this->_null_style_operators
6109
-        );
6110
-    }
6111
-
6112
-
6113
-    /**
6114
-     * Gets an array where keys are the primary keys and values are their 'names'
6115
-     * (as determined by the model object's name() function, which is often overridden)
6116
-     *
6117
-     * @param array $query_params like get_all's
6118
-     * @return string[]
6119
-     * @throws EE_Error
6120
-     * @throws ReflectionException
6121
-     */
6122
-    public function get_all_names($query_params = [])
6123
-    {
6124
-        $objs  = $this->get_all($query_params);
6125
-        $names = [];
6126
-        foreach ($objs as $obj) {
6127
-            $names[ $obj->ID() ] = $obj->name();
6128
-        }
6129
-        return $names;
6130
-    }
6131
-
6132
-
6133
-    /**
6134
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6135
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6136
-     * this is duplicated effort and reduces efficiency) you would be better to use
6137
-     * array_keys() on $model_objects.
6138
-     *
6139
-     * @param EE_Base_Class[] $model_objects
6140
-     * @param boolean         $filter_out_empty_ids  if a model object has an ID of '' or 0, don't bother including it
6141
-     *                                               in the returned array
6142
-     * @return array
6143
-     * @throws EE_Error
6144
-     * @throws ReflectionException
6145
-     */
6146
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6147
-    {
6148
-        if (! $this->has_primary_key_field()) {
6149
-            if (WP_DEBUG) {
6150
-                EE_Error::add_error(
6151
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6152
-                    __FILE__,
6153
-                    __FUNCTION__,
6154
-                    __LINE__
6155
-                );
6156
-            }
6157
-        }
6158
-        $IDs = [];
6159
-        foreach ($model_objects as $model_object) {
6160
-            $id = $model_object->ID();
6161
-            if (! $id) {
6162
-                if ($filter_out_empty_ids) {
6163
-                    continue;
6164
-                }
6165
-                if (WP_DEBUG) {
6166
-                    EE_Error::add_error(
6167
-                        esc_html__(
6168
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6169
-                            'event_espresso'
6170
-                        ),
6171
-                        __FILE__,
6172
-                        __FUNCTION__,
6173
-                        __LINE__
6174
-                    );
6175
-                }
6176
-            }
6177
-            $IDs[] = $id;
6178
-        }
6179
-        return $IDs;
6180
-    }
6181
-
6182
-
6183
-    /**
6184
-     * Returns the string used in capabilities relating to this model. If there
6185
-     * are no capabilities that relate to this model returns false
6186
-     *
6187
-     * @return string|false
6188
-     */
6189
-    public function cap_slug()
6190
-    {
6191
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6192
-    }
6193
-
6194
-
6195
-    /**
6196
-     * Returns the capability-restrictions array (@param string $context
6197
-     *
6198
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6199
-     * @throws EE_Error
6200
-     * @see EEM_Base::_cap_restrictions).
6201
-     *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6202
-     *      only returns the cap restrictions array in that context (ie, the array
6203
-     *      at that key)
6204
-     *
6205
-     */
6206
-    public function cap_restrictions($context = EEM_Base::caps_read)
6207
-    {
6208
-        EEM_Base::verify_is_valid_cap_context($context);
6209
-        // check if we ought to run the restriction generator first
6210
-        if (
6211
-            isset($this->_cap_restriction_generators[ $context ])
6212
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6213
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6214
-        ) {
6215
-            $this->_cap_restrictions[ $context ] = array_merge(
6216
-                $this->_cap_restrictions[ $context ],
6217
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6218
-            );
6219
-        }
6220
-        // and make sure we've finalized the construction of each restriction
6221
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6222
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6223
-                $where_conditions_obj->_finalize_construct($this);
6224
-            }
6225
-        }
6226
-        return $this->_cap_restrictions[ $context ];
6227
-    }
6228
-
6229
-
6230
-    /**
6231
-     * Indicating whether or not this model thinks its a wp core model
6232
-     *
6233
-     * @return boolean
6234
-     */
6235
-    public function is_wp_core_model()
6236
-    {
6237
-        return $this->_wp_core_model;
6238
-    }
6239
-
6240
-
6241
-    /**
6242
-     * Gets all the caps that are missing which impose a restriction on
6243
-     * queries made in this context
6244
-     *
6245
-     * @param string $context one of EEM_Base::caps_ constants
6246
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6247
-     * @throws EE_Error
6248
-     */
6249
-    public function caps_missing($context = EEM_Base::caps_read)
6250
-    {
6251
-        $missing_caps     = [];
6252
-        $cap_restrictions = $this->cap_restrictions($context);
6253
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6254
-            if (
6255
-            ! EE_Capabilities::instance()
6256
-                             ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6257
-            ) {
6258
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6259
-            }
6260
-        }
6261
-        return $missing_caps;
6262
-    }
6263
-
6264
-
6265
-    /**
6266
-     * Gets the mapping from capability contexts to action strings used in capability names
6267
-     *
6268
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6269
-     * one of 'read', 'edit', or 'delete'
6270
-     */
6271
-    public function cap_contexts_to_cap_action_map()
6272
-    {
6273
-        return apply_filters(
6274
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6275
-            $this->_cap_contexts_to_cap_action_map,
6276
-            $this
6277
-        );
6278
-    }
6279
-
6280
-
6281
-    /**
6282
-     * Gets the action string for the specified capability context
6283
-     *
6284
-     * @param string $context
6285
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6286
-     * @throws EE_Error
6287
-     */
6288
-    public function cap_action_for_context($context)
6289
-    {
6290
-        $mapping = $this->cap_contexts_to_cap_action_map();
6291
-        if (isset($mapping[ $context ])) {
6292
-            return $mapping[ $context ];
6293
-        }
6294
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6295
-            return $action;
6296
-        }
6297
-        throw new EE_Error(
6298
-            sprintf(
6299
-                esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6300
-                $context,
6301
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6302
-            )
6303
-        );
6304
-    }
6305
-
6306
-
6307
-    /**
6308
-     * Returns all the capability contexts which are valid when querying models
6309
-     *
6310
-     * @return array
6311
-     */
6312
-    public static function valid_cap_contexts()
6313
-    {
6314
-        return apply_filters(
6315
-            'FHEE__EEM_Base__valid_cap_contexts',
6316
-            [
6317
-                self::caps_read,
6318
-                self::caps_read_admin,
6319
-                self::caps_edit,
6320
-                self::caps_delete,
6321
-            ]
6322
-        );
6323
-    }
6324
-
6325
-
6326
-    /**
6327
-     * Returns all valid options for 'default_where_conditions'
6328
-     *
6329
-     * @return array
6330
-     */
6331
-    public static function valid_default_where_conditions()
6332
-    {
6333
-        return [
6334
-            EEM_Base::default_where_conditions_all,
6335
-            EEM_Base::default_where_conditions_this_only,
6336
-            EEM_Base::default_where_conditions_others_only,
6337
-            EEM_Base::default_where_conditions_minimum_all,
6338
-            EEM_Base::default_where_conditions_minimum_others,
6339
-            EEM_Base::default_where_conditions_none,
6340
-        ];
6341
-    }
6342
-
6343
-    // public static function default_where_conditions_full
6344
-
6345
-
6346
-    /**
6347
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6348
-     *
6349
-     * @param string $context
6350
-     * @return bool
6351
-     * @throws EE_Error
6352
-     */
6353
-    public static function verify_is_valid_cap_context($context)
6354
-    {
6355
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6356
-        if (in_array($context, $valid_cap_contexts)) {
6357
-            return true;
6358
-        }
6359
-        throw new EE_Error(
6360
-            sprintf(
6361
-                esc_html__(
6362
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6363
-                    'event_espresso'
6364
-                ),
6365
-                $context,
6366
-                'EEM_Base',
6367
-                implode(',', $valid_cap_contexts)
6368
-            )
6369
-        );
6370
-    }
6371
-
6372
-
6373
-    /**
6374
-     * Clears all the models field caches. This is only useful when a sub-class
6375
-     * might have added a field or something and these caches might be invalidated
6376
-     */
6377
-    protected function _invalidate_field_caches()
6378
-    {
6379
-        $this->_cache_foreign_key_to_fields = [];
6380
-        $this->_cached_fields               = null;
6381
-        $this->_cached_fields_non_db_only   = null;
6382
-    }
6383
-
6384
-
6385
-    /**
6386
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6387
-     * (eg "and", "or", "not").
6388
-     *
6389
-     * @return array
6390
-     */
6391
-    public function logic_query_param_keys()
6392
-    {
6393
-        return $this->_logic_query_param_keys;
6394
-    }
6395
-
6396
-
6397
-    /**
6398
-     * Determines whether or not the where query param array key is for a logic query param.
6399
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6400
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6401
-     *
6402
-     * @param $query_param_key
6403
-     * @return bool
6404
-     */
6405
-    public function is_logic_query_param_key($query_param_key)
6406
-    {
6407
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6408
-            if (
6409
-                $query_param_key === $logic_query_param_key
6410
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6411
-            ) {
6412
-                return true;
6413
-            }
6414
-        }
6415
-        return false;
6416
-    }
6417
-
6418
-
6419
-    /**
6420
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6421
-     *
6422
-     * @return boolean
6423
-     * @since 4.9.74.p
6424
-     */
6425
-    public function hasPassword()
6426
-    {
6427
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6428
-        if ($this->has_password_field === null) {
6429
-            $password_field           = $this->getPasswordField();
6430
-            $this->has_password_field = $password_field instanceof EE_Password_Field;
6431
-        }
6432
-        return $this->has_password_field;
6433
-    }
6434
-
6435
-
6436
-    /**
6437
-     * Returns the password field on this model, if there is one
6438
-     *
6439
-     * @return EE_Password_Field|null
6440
-     * @since 4.9.74.p
6441
-     */
6442
-    public function getPasswordField()
6443
-    {
6444
-        // if we definitely already know there is a password field or not (because has_password_field is true or false)
6445
-        // there's no need to search for it. If we don't know yet, then find out
6446
-        if ($this->has_password_field === null && $this->password_field === null) {
6447
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6448
-        }
6449
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6450
-        return $this->password_field;
6451
-    }
6452
-
6453
-
6454
-    /**
6455
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6456
-     *
6457
-     * @return EE_Model_Field_Base[]
6458
-     * @throws EE_Error
6459
-     * @since 4.9.74.p
6460
-     */
6461
-    public function getPasswordProtectedFields()
6462
-    {
6463
-        $password_field = $this->getPasswordField();
6464
-        $fields         = [];
6465
-        if ($password_field instanceof EE_Password_Field) {
6466
-            $field_names = $password_field->protectedFields();
6467
-            foreach ($field_names as $field_name) {
6468
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6469
-            }
6470
-        }
6471
-        return $fields;
6472
-    }
6473
-
6474
-
6475
-    /**
6476
-     * Checks if the current user can perform the requested action on this model
6477
-     *
6478
-     * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6479
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6480
-     * @return bool
6481
-     * @throws EE_Error
6482
-     * @throws InvalidArgumentException
6483
-     * @throws InvalidDataTypeException
6484
-     * @throws InvalidInterfaceException
6485
-     * @throws ReflectionException
6486
-     * @throws UnexpectedEntityException
6487
-     * @since 4.9.74.p
6488
-     */
6489
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6490
-    {
6491
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6492
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6493
-        }
6494
-        if (! is_array($model_obj_or_fields_n_values)) {
6495
-            throw new UnexpectedEntityException(
6496
-                $model_obj_or_fields_n_values,
6497
-                'EE_Base_Class',
6498
-                sprintf(
6499
-                    esc_html__(
6500
-                        '%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6501
-                        'event_espresso'
6502
-                    ),
6503
-                    __FUNCTION__
6504
-                )
6505
-            );
6506
-        }
6507
-        return $this->exists(
6508
-            $this->alter_query_params_to_restrict_by_ID(
6509
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6510
-                [
6511
-                    'default_where_conditions' => 'none',
6512
-                    'caps'                     => $cap_to_check,
6513
-                ]
6514
-            )
6515
-        );
6516
-    }
6517
-
6518
-
6519
-    /**
6520
-     * Returns the query param where conditions key to the password affecting this model.
6521
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6522
-     *
6523
-     * @return null|string
6524
-     * @throws EE_Error
6525
-     * @throws InvalidArgumentException
6526
-     * @throws InvalidDataTypeException
6527
-     * @throws InvalidInterfaceException
6528
-     * @throws ModelConfigurationException
6529
-     * @throws ReflectionException
6530
-     * @since 4.9.74.p
6531
-     */
6532
-    public function modelChainAndPassword()
6533
-    {
6534
-        if ($this->model_chain_to_password === null) {
6535
-            throw new ModelConfigurationException(
6536
-                $this,
6537
-                esc_html_x(
6538
-                // @codingStandardsIgnoreStart
6539
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6540
-                    // @codingStandardsIgnoreEnd
6541
-                    '1: model name',
6542
-                    'event_espresso'
6543
-                )
6544
-            );
6545
-        }
6546
-        if ($this->model_chain_to_password === '') {
6547
-            $model_with_password = $this;
6548
-        } else {
6549
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6550
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6551
-            } else {
6552
-                $last_model_in_chain = $this->model_chain_to_password;
6553
-            }
6554
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6555
-        }
6556
-
6557
-        $password_field = $model_with_password->getPasswordField();
6558
-        if ($password_field instanceof EE_Password_Field) {
6559
-            $password_field_name = $password_field->get_name();
6560
-        } else {
6561
-            throw new ModelConfigurationException(
6562
-                $this,
6563
-                sprintf(
6564
-                    esc_html_x(
6565
-                        'This model claims related model "%1$s" should have a password field on it, but none was found. The model relation chain is "%2$s"',
6566
-                        '1: model name, 2: special string',
6567
-                        'event_espresso'
6568
-                    ),
6569
-                    $model_with_password->get_this_model_name(),
6570
-                    $this->model_chain_to_password
6571
-                )
6572
-            );
6573
-        }
6574
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6575
-    }
6576
-
6577
-
6578
-    /**
6579
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6580
-     * or if this model itself has a password affecting access to some of its other fields.
6581
-     *
6582
-     * @return boolean
6583
-     * @since 4.9.74.p
6584
-     */
6585
-    public function restrictedByRelatedModelPassword()
6586
-    {
6587
-        return $this->model_chain_to_password !== null;
6588
-    }
3867
+		}
3868
+		return $null_friendly_where_conditions;
3869
+	}
3870
+
3871
+
3872
+	/**
3873
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3874
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3875
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3876
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3877
+	 *
3878
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3879
+	 * @return array
3880
+	 * @throws EE_Error
3881
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3882
+	 */
3883
+	private function _get_default_where_conditions($model_relation_path = '')
3884
+	{
3885
+		if ($this->_ignore_where_strategy) {
3886
+			return [];
3887
+		}
3888
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3889
+	}
3890
+
3891
+
3892
+	/**
3893
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3894
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3895
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3896
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3897
+	 * Similar to _get_default_where_conditions
3898
+	 *
3899
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3900
+	 * @return array
3901
+	 * @throws EE_Error
3902
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3903
+	 */
3904
+	protected function _get_minimum_where_conditions($model_relation_path = '')
3905
+	{
3906
+		if ($this->_ignore_where_strategy) {
3907
+			return [];
3908
+		}
3909
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3910
+	}
3911
+
3912
+
3913
+	/**
3914
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3915
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3916
+	 *
3917
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
3918
+	 * @return string
3919
+	 * @throws EE_Error
3920
+	 */
3921
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3922
+	{
3923
+		$selects = $this->_get_columns_to_select_for_this_model();
3924
+		foreach (
3925
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3926
+		) {
3927
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3928
+			$other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3929
+			foreach ($other_model_selects as $key => $value) {
3930
+				$selects[] = $value;
3931
+			}
3932
+		}
3933
+		return implode(", ", $selects);
3934
+	}
3935
+
3936
+
3937
+	/**
3938
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3939
+	 * So that's going to be the columns for all the fields on the model
3940
+	 *
3941
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
3942
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3943
+	 */
3944
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3945
+	{
3946
+		$fields                                       = $this->field_settings();
3947
+		$selects                                      = [];
3948
+		$table_alias_with_model_relation_chain_prefix =
3949
+			EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3950
+				$model_relation_chain,
3951
+				$this->get_this_model_name()
3952
+			);
3953
+		foreach ($fields as $field_obj) {
3954
+			$selects[] = $table_alias_with_model_relation_chain_prefix
3955
+						 . $field_obj->get_table_alias()
3956
+						 . "."
3957
+						 . $field_obj->get_table_column()
3958
+						 . " AS '"
3959
+						 . $table_alias_with_model_relation_chain_prefix
3960
+						 . $field_obj->get_table_alias()
3961
+						 . "."
3962
+						 . $field_obj->get_table_column()
3963
+						 . "'";
3964
+		}
3965
+		// make sure we are also getting the PKs of each table
3966
+		$tables = $this->get_tables();
3967
+		if (count($tables) > 1) {
3968
+			foreach ($tables as $table_obj) {
3969
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3970
+									   . $table_obj->get_fully_qualified_pk_column();
3971
+				if (! in_array($qualified_pk_column, $selects)) {
3972
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3973
+				}
3974
+			}
3975
+		}
3976
+		return $selects;
3977
+	}
3978
+
3979
+
3980
+	/**
3981
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3982
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3983
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3984
+	 * SQL for joining, and the data types
3985
+	 *
3986
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
3987
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3988
+	 * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
3989
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3990
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
3991
+	 *                                                          or 'Registration's
3992
+	 * @param string|null                 $original_query_param what it originally was (eg
3993
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3994
+	 *                                                          matches $query_param
3995
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3996
+	 * @throws EE_Error
3997
+	 */
3998
+	private function _extract_related_model_info_from_query_param(
3999
+		$query_param,
4000
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4001
+		$query_param_type,
4002
+		$original_query_param = null
4003
+	) {
4004
+		if ($original_query_param === null) {
4005
+			$original_query_param = $query_param;
4006
+		}
4007
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4008
+		// whether or not to allow logic_query_params like 'NOT','OR', or 'AND'
4009
+		$allow_logic_query_params = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4010
+		$allow_fields             = in_array(
4011
+			$query_param_type,
4012
+			['where', 'having', 'order_by', 'group_by', 'order', 'custom_selects', 0],
4013
+			true
4014
+		);
4015
+		// check to see if we have a field on this model
4016
+		$this_model_fields = $this->field_settings(true);
4017
+		if (array_key_exists($query_param, $this_model_fields)) {
4018
+			if ($allow_fields) {
4019
+				return;
4020
+			}
4021
+			throw new EE_Error(
4022
+				sprintf(
4023
+					esc_html__(
4024
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4025
+						"event_espresso"
4026
+					),
4027
+					$query_param,
4028
+					get_class($this),
4029
+					$query_param_type,
4030
+					$original_query_param
4031
+				)
4032
+			);
4033
+		}
4034
+		// check if this is a special logic query param
4035
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4036
+			if ($allow_logic_query_params) {
4037
+				return;
4038
+			}
4039
+			throw new EE_Error(
4040
+				sprintf(
4041
+					esc_html__(
4042
+						'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4043
+						'event_espresso'
4044
+					),
4045
+					implode('", "', $this->_logic_query_param_keys),
4046
+					$query_param,
4047
+					get_class($this),
4048
+					'<br />',
4049
+					"\t"
4050
+					. ' $passed_in_query_info = <pre>'
4051
+					. print_r($passed_in_query_info, true)
4052
+					. '</pre>'
4053
+					. "\n\t"
4054
+					. ' $query_param_type = '
4055
+					. $query_param_type
4056
+					. "\n\t"
4057
+					. ' $original_query_param = '
4058
+					. $original_query_param
4059
+				)
4060
+			);
4061
+		}
4062
+		// check if it's a custom selection
4063
+		if (
4064
+			$this->_custom_selections instanceof CustomSelects
4065
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4066
+		) {
4067
+			return;
4068
+		}
4069
+		// check if has a model name at the beginning
4070
+		// and
4071
+		// check if it's a field on a related model
4072
+		if (
4073
+		$this->extractJoinModelFromQueryParams(
4074
+			$passed_in_query_info,
4075
+			$query_param,
4076
+			$original_query_param,
4077
+			$query_param_type
4078
+		)
4079
+		) {
4080
+			return;
4081
+		}
4082
+
4083
+		// ok so $query_param didn't start with a model name
4084
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4085
+		// it's wack, that's what it is
4086
+		throw new EE_Error(
4087
+			sprintf(
4088
+				esc_html__(
4089
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4090
+					"event_espresso"
4091
+				),
4092
+				$query_param,
4093
+				get_class($this),
4094
+				$query_param_type,
4095
+				$original_query_param
4096
+			)
4097
+		);
4098
+	}
4099
+
4100
+
4101
+	/**
4102
+	 * Extracts any possible join model information from the provided possible_join_string.
4103
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model
4104
+	 * join
4105
+	 * parts that should be added to the query.
4106
+	 *
4107
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4108
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4109
+	 * @param null|string                 $original_query_param
4110
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4111
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4112
+	 *                                                           etc.)
4113
+	 * @return bool  returns true if a join was added and false if not.
4114
+	 * @throws EE_Error
4115
+	 */
4116
+	private function extractJoinModelFromQueryParams(
4117
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4118
+		$possible_join_string,
4119
+		$original_query_param,
4120
+		$query_parameter_type
4121
+	) {
4122
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4123
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4124
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4125
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4126
+				if ($possible_join_string === '') {
4127
+					// nothing left to $query_param
4128
+					// we should actually end in a field name, not a model like this!
4129
+					throw new EE_Error(
4130
+						sprintf(
4131
+							esc_html__(
4132
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4133
+								"event_espresso"
4134
+							),
4135
+							$possible_join_string,
4136
+							$query_parameter_type,
4137
+							get_class($this),
4138
+							$valid_related_model_name
4139
+						)
4140
+					);
4141
+				}
4142
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4143
+				$related_model_obj->_extract_related_model_info_from_query_param(
4144
+					$possible_join_string,
4145
+					$query_info_carrier,
4146
+					$query_parameter_type,
4147
+					$original_query_param
4148
+				);
4149
+				return true;
4150
+			}
4151
+			if ($possible_join_string === $valid_related_model_name) {
4152
+				$this->_add_join_to_model(
4153
+					$valid_related_model_name,
4154
+					$query_info_carrier,
4155
+					$original_query_param
4156
+				);
4157
+				return true;
4158
+			}
4159
+		}
4160
+		return false;
4161
+	}
4162
+
4163
+
4164
+	/**
4165
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4166
+	 *
4167
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4168
+	 * @throws EE_Error
4169
+	 */
4170
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4171
+	{
4172
+		if (
4173
+			$this->_custom_selections instanceof CustomSelects
4174
+			&& ($this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4175
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4176
+			)
4177
+		) {
4178
+			$original_selects = $this->_custom_selections->originalSelects();
4179
+			foreach ($original_selects as $alias => $select_configuration) {
4180
+				$this->extractJoinModelFromQueryParams(
4181
+					$query_info_carrier,
4182
+					$select_configuration[0],
4183
+					$select_configuration[0],
4184
+					'custom_selects'
4185
+				);
4186
+			}
4187
+		}
4188
+	}
4189
+
4190
+
4191
+	/**
4192
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4193
+	 * and store it on $passed_in_query_info
4194
+	 *
4195
+	 * @param string                      $model_name
4196
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4197
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4198
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4199
+	 *                                                          and are adding a join to 'Payment' with the original
4200
+	 *                                                          query param key
4201
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4202
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4203
+	 *                                                          Payment wants to add default query params so that it
4204
+	 *                                                          will know what models to prepend onto its default query
4205
+	 *                                                          params or in case it wants to rename tables (in case
4206
+	 *                                                          there are multiple joins to the same table)
4207
+	 * @return void
4208
+	 * @throws EE_Error
4209
+	 */
4210
+	private function _add_join_to_model(
4211
+		$model_name,
4212
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4213
+		$original_query_param
4214
+	) {
4215
+		$relation_obj         = $this->related_settings_for($model_name);
4216
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4217
+		// check if the relation is HABTM, because then we're essentially doing two joins
4218
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4219
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4220
+			$join_model_obj = $relation_obj->get_join_model();
4221
+			// replace the model specified with the join model for this relation chain, whi
4222
+			$relation_chain_to_join_model =
4223
+				EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4224
+					$model_name,
4225
+					$join_model_obj->get_this_model_name(),
4226
+					$model_relation_chain
4227
+				);
4228
+			$passed_in_query_info->merge(
4229
+				new EE_Model_Query_Info_Carrier(
4230
+					[$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4231
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4232
+				)
4233
+			);
4234
+		}
4235
+		// now just join to the other table pointed to by the relation object, and add its data types
4236
+		$passed_in_query_info->merge(
4237
+			new EE_Model_Query_Info_Carrier(
4238
+				[$model_relation_chain => $model_name],
4239
+				$relation_obj->get_join_statement($model_relation_chain)
4240
+			)
4241
+		);
4242
+	}
4243
+
4244
+
4245
+	/**
4246
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4247
+	 *
4248
+	 * @param array $where_params
4249
+	 * @return string of SQL
4250
+	 * @throws EE_Error
4251
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4252
+	 */
4253
+	private function _construct_where_clause($where_params)
4254
+	{
4255
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4256
+		if ($SQL) {
4257
+			return " WHERE " . $SQL;
4258
+		}
4259
+		return '';
4260
+	}
4261
+
4262
+
4263
+	/**
4264
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4265
+	 * and should be passed HAVING parameters, not WHERE parameters
4266
+	 *
4267
+	 * @param array $having_params
4268
+	 * @return string
4269
+	 * @throws EE_Error
4270
+	 */
4271
+	private function _construct_having_clause($having_params)
4272
+	{
4273
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4274
+		if ($SQL) {
4275
+			return " HAVING " . $SQL;
4276
+		}
4277
+		return '';
4278
+	}
4279
+
4280
+
4281
+	/**
4282
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4283
+	 * Event_Meta.meta_value = 'foo'))"
4284
+	 *
4285
+	 * @param array  $where_params
4286
+	 * @param string $glue joins each sub-clause together. Should really only be " AND " or " OR "...
4287
+	 * @return string of SQL
4288
+	 * @throws EE_Error
4289
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4290
+	 */
4291
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4292
+	{
4293
+		$where_clauses = [];
4294
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4295
+			$query_param =
4296
+				$this->_remove_stars_and_anything_after_from_condition_query_param_key(
4297
+					$query_param
4298
+				);// str_replace("*",'',$query_param);
4299
+			if (in_array($query_param, $this->_logic_query_param_keys)) {
4300
+				switch ($query_param) {
4301
+					case 'not':
4302
+					case 'NOT':
4303
+						$where_clauses[] = "! ("
4304
+										   . $this->_construct_condition_clause_recursive(
4305
+								$op_and_value_or_sub_condition,
4306
+								$glue
4307
+							)
4308
+										   . ")";
4309
+						break;
4310
+					case 'and':
4311
+					case 'AND':
4312
+						$where_clauses[] = " ("
4313
+										   . $this->_construct_condition_clause_recursive(
4314
+								$op_and_value_or_sub_condition,
4315
+								' AND '
4316
+							)
4317
+										   . ")";
4318
+						break;
4319
+					case 'or':
4320
+					case 'OR':
4321
+						$where_clauses[] = " ("
4322
+										   . $this->_construct_condition_clause_recursive(
4323
+								$op_and_value_or_sub_condition,
4324
+								' OR '
4325
+							)
4326
+										   . ")";
4327
+						break;
4328
+				}
4329
+			} else {
4330
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4331
+				// if it's not a normal field, maybe it's a custom selection?
4332
+				if (! $field_obj) {
4333
+					if ($this->_custom_selections instanceof CustomSelects) {
4334
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4335
+					} else {
4336
+						throw new EE_Error(
4337
+							sprintf(
4338
+								esc_html__(
4339
+									"%s is neither a valid model field name, nor a custom selection",
4340
+									"event_espresso"
4341
+								),
4342
+								$query_param
4343
+							)
4344
+						);
4345
+					}
4346
+				}
4347
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4348
+				$where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4349
+			}
4350
+		}
4351
+		return $where_clauses ? implode($glue, $where_clauses) : '';
4352
+	}
4353
+
4354
+
4355
+	/**
4356
+	 * Takes the input parameter and extract the table name (alias) and column name
4357
+	 *
4358
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4359
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4360
+	 * @throws EE_Error
4361
+	 */
4362
+	private function _deduce_column_name_from_query_param($query_param)
4363
+	{
4364
+		$field = $this->_deduce_field_from_query_param($query_param);
4365
+		if ($field) {
4366
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4367
+				$field->get_model_name(),
4368
+				$query_param
4369
+			);
4370
+			return $table_alias_prefix . $field->get_qualified_column();
4371
+		}
4372
+		if (
4373
+			$this->_custom_selections instanceof CustomSelects
4374
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4375
+		) {
4376
+			// maybe it's custom selection item?
4377
+			// if so, just use it as the "column name"
4378
+			return $query_param;
4379
+		}
4380
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4381
+			? implode(',', $this->_custom_selections->columnAliases())
4382
+			: '';
4383
+		throw new EE_Error(
4384
+			sprintf(
4385
+				esc_html__(
4386
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4387
+					"event_espresso"
4388
+				),
4389
+				$query_param,
4390
+				$custom_select_aliases
4391
+			)
4392
+		);
4393
+	}
4394
+
4395
+
4396
+	/**
4397
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4398
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4399
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4400
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4401
+	 *
4402
+	 * @param string $condition_query_param_key
4403
+	 * @return string
4404
+	 */
4405
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4406
+	{
4407
+		$pos_of_star = strpos($condition_query_param_key, '*');
4408
+		if ($pos_of_star === false) {
4409
+			return $condition_query_param_key;
4410
+		}
4411
+		return substr($condition_query_param_key, 0, $pos_of_star);
4412
+	}
4413
+
4414
+
4415
+	/**
4416
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4417
+	 *
4418
+	 * @param mixed      array | string    $op_and_value
4419
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4420
+	 * @return string
4421
+	 * @throws EE_Error
4422
+	 */
4423
+	private function _construct_op_and_value($op_and_value, $field_obj)
4424
+	{
4425
+		if (is_array($op_and_value)) {
4426
+			$operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4427
+			if (! $operator) {
4428
+				$php_array_like_string = [];
4429
+				foreach ($op_and_value as $key => $value) {
4430
+					$php_array_like_string[] = "$key=>$value";
4431
+				}
4432
+				throw new EE_Error(
4433
+					sprintf(
4434
+						esc_html__(
4435
+							"You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4436
+							"event_espresso"
4437
+						),
4438
+						implode(",", $php_array_like_string)
4439
+					)
4440
+				);
4441
+			}
4442
+			$value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4443
+		} else {
4444
+			$operator = '=';
4445
+			$value    = $op_and_value;
4446
+		}
4447
+		// check to see if the value is actually another field
4448
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4449
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4450
+		}
4451
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4452
+			// in this case, the value should be an array, or at least a comma-separated list
4453
+			// it will need to handle a little differently
4454
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4455
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4456
+			return $operator . SP . $cleaned_value;
4457
+		}
4458
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4459
+			// the value should be an array with count of two.
4460
+			if (count($value) !== 2) {
4461
+				throw new EE_Error(
4462
+					sprintf(
4463
+						esc_html__(
4464
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4465
+							'event_espresso'
4466
+						),
4467
+						"BETWEEN"
4468
+					)
4469
+				);
4470
+			}
4471
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4472
+			return $operator . SP . $cleaned_value;
4473
+		}
4474
+		if (in_array($operator, $this->valid_null_style_operators())) {
4475
+			if ($value !== null) {
4476
+				throw new EE_Error(
4477
+					sprintf(
4478
+						esc_html__(
4479
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4480
+							"event_espresso"
4481
+						),
4482
+						$value,
4483
+						$operator
4484
+					)
4485
+				);
4486
+			}
4487
+			return $operator;
4488
+		}
4489
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4490
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4491
+			// remove other junk. So just treat it as a string.
4492
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4493
+		}
4494
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4495
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4496
+		}
4497
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4498
+			throw new EE_Error(
4499
+				sprintf(
4500
+					esc_html__(
4501
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4502
+						'event_espresso'
4503
+					),
4504
+					$operator,
4505
+					$operator
4506
+				)
4507
+			);
4508
+		}
4509
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4510
+			throw new EE_Error(
4511
+				sprintf(
4512
+					esc_html__(
4513
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4514
+						'event_espresso'
4515
+					),
4516
+					$operator,
4517
+					$operator
4518
+				)
4519
+			);
4520
+		}
4521
+		throw new EE_Error(
4522
+			sprintf(
4523
+				esc_html__(
4524
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4525
+					"event_espresso"
4526
+				),
4527
+				http_build_query($op_and_value)
4528
+			)
4529
+		);
4530
+	}
4531
+
4532
+
4533
+	/**
4534
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4535
+	 *
4536
+	 * @param array                      $values
4537
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4538
+	 *                                              '%s'
4539
+	 * @return string
4540
+	 * @throws EE_Error
4541
+	 */
4542
+	public function _construct_between_value($values, $field_obj)
4543
+	{
4544
+		$cleaned_values = [];
4545
+		foreach ($values as $value) {
4546
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4547
+		}
4548
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4549
+	}
4550
+
4551
+
4552
+	/**
4553
+	 * Takes an array or a comma-separated list of $values and cleans them
4554
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4555
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4556
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4557
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4558
+	 *
4559
+	 * @param mixed                      $values    array or comma-separated string
4560
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4561
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4562
+	 * @throws EE_Error
4563
+	 */
4564
+	public function _construct_in_value($values, $field_obj)
4565
+	{
4566
+		// check if the value is a CSV list
4567
+		if (is_string($values)) {
4568
+			// in which case, turn it into an array
4569
+			$values = explode(",", $values);
4570
+		}
4571
+		$cleaned_values = [];
4572
+		foreach ($values as $value) {
4573
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4574
+		}
4575
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4576
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4577
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4578
+		if (empty($cleaned_values)) {
4579
+			$all_fields       = $this->field_settings();
4580
+			$a_field          = array_shift($all_fields);
4581
+			$main_table       = $this->_get_main_table();
4582
+			$cleaned_values[] = "SELECT "
4583
+								. $a_field->get_table_column()
4584
+								. " FROM "
4585
+								. $main_table->get_table_name()
4586
+								. " WHERE FALSE";
4587
+		}
4588
+		return "(" . implode(",", $cleaned_values) . ")";
4589
+	}
4590
+
4591
+
4592
+	/**
4593
+	 * @param mixed                      $value
4594
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4595
+	 * @return false|null|string
4596
+	 * @throws EE_Error
4597
+	 */
4598
+	private function _wpdb_prepare_using_field($value, $field_obj)
4599
+	{
4600
+		global $wpdb;
4601
+		if ($field_obj instanceof EE_Model_Field_Base) {
4602
+			return $wpdb->prepare(
4603
+				$field_obj->get_wpdb_data_type(),
4604
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4605
+			);
4606
+		} //$field_obj should really just be a data type
4607
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4608
+			throw new EE_Error(
4609
+				sprintf(
4610
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4611
+					$field_obj,
4612
+					implode(",", $this->_valid_wpdb_data_types)
4613
+				)
4614
+			);
4615
+		}
4616
+		return $wpdb->prepare($field_obj, $value);
4617
+	}
4618
+
4619
+
4620
+	/**
4621
+	 * Takes the input parameter and finds the model field that it indicates.
4622
+	 *
4623
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4624
+	 * @return EE_Model_Field_Base
4625
+	 * @throws EE_Error
4626
+	 */
4627
+	protected function _deduce_field_from_query_param($query_param_name)
4628
+	{
4629
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4630
+		// which will help us find the database table and column
4631
+		$query_param_parts = explode(".", $query_param_name);
4632
+		if (empty($query_param_parts)) {
4633
+			throw new EE_Error(
4634
+				sprintf(
4635
+					esc_html__(
4636
+						"_extract_column_name is empty when trying to extract column and table name from %s",
4637
+						'event_espresso'
4638
+					),
4639
+					$query_param_name
4640
+				)
4641
+			);
4642
+		}
4643
+		$number_of_parts       = count($query_param_parts);
4644
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4645
+		if ($number_of_parts === 1) {
4646
+			$field_name = $last_query_param_part;
4647
+			$model_obj  = $this;
4648
+		} else {// $number_of_parts >= 2
4649
+			// the last part is the column name, and there are only 2parts. therefore...
4650
+			$field_name = $last_query_param_part;
4651
+			$model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4652
+		}
4653
+		try {
4654
+			return $model_obj->field_settings_for($field_name);
4655
+		} catch (EE_Error $e) {
4656
+			return null;
4657
+		}
4658
+	}
4659
+
4660
+
4661
+	/**
4662
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4663
+	 * alias and column which corresponds to it
4664
+	 *
4665
+	 * @param string $field_name
4666
+	 * @return string
4667
+	 * @throws EE_Error
4668
+	 */
4669
+	public function _get_qualified_column_for_field($field_name)
4670
+	{
4671
+		$all_fields = $this->field_settings();
4672
+		$field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4673
+		if ($field) {
4674
+			return $field->get_qualified_column();
4675
+		}
4676
+		throw new EE_Error(
4677
+			sprintf(
4678
+				esc_html__(
4679
+					"There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4680
+					'event_espresso'
4681
+				),
4682
+				$field_name,
4683
+				get_class($this)
4684
+			)
4685
+		);
4686
+	}
4687
+
4688
+
4689
+	/**
4690
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4691
+	 * Example usage:
4692
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4693
+	 *      array(),
4694
+	 *      ARRAY_A,
4695
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4696
+	 *  );
4697
+	 * is equivalent to
4698
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4699
+	 * and
4700
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4701
+	 *      array(
4702
+	 *          array(
4703
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4704
+	 *          ),
4705
+	 *          ARRAY_A,
4706
+	 *          implode(
4707
+	 *              ', ',
4708
+	 *              array_merge(
4709
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4710
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4711
+	 *              )
4712
+	 *          )
4713
+	 *      )
4714
+	 *  );
4715
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4716
+	 *
4717
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4718
+	 *                                            and the one whose fields you are selecting for example: when querying
4719
+	 *                                            tickets model and selecting fields from the tickets model you would
4720
+	 *                                            leave this parameter empty, because no models are needed to join
4721
+	 *                                            between the queried model and the selected one. Likewise when
4722
+	 *                                            querying the datetime model and selecting fields from the tickets
4723
+	 *                                            model, it would also be left empty, because there is a direct
4724
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4725
+	 *                                            them together. However, when querying from the event model and
4726
+	 *                                            selecting fields from the ticket model, you should provide the string
4727
+	 *                                            'Datetime', indicating that the event model must first join to the
4728
+	 *                                            datetime model in order to find its relation to ticket model.
4729
+	 *                                            Also, when querying from the venue model and selecting fields from
4730
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4731
+	 *                                            indicating you need to join the venue model to the event model,
4732
+	 *                                            to the datetime model, in order to find its relation to the ticket
4733
+	 *                                            model. This string is used to deduce the prefix that gets added onto
4734
+	 *                                            the models' tables qualified columns
4735
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4736
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4737
+	 *                                            qualified column names
4738
+	 * @return array|string
4739
+	 */
4740
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4741
+	{
4742
+		$table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4743
+		$qualified_columns = [];
4744
+		foreach ($this->field_settings() as $field_name => $field) {
4745
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4746
+		}
4747
+		return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4748
+	}
4749
+
4750
+
4751
+	/**
4752
+	 * constructs the select use on special limit joins
4753
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4754
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4755
+	 * (as that is typically where the limits would be set).
4756
+	 *
4757
+	 * @param string       $table_alias The table the select is being built for
4758
+	 * @param mixed|string $limit       The limit for this select
4759
+	 * @return string                The final select join element for the query.
4760
+	 * @throws EE_Error
4761
+	 */
4762
+	public function _construct_limit_join_select($table_alias, $limit)
4763
+	{
4764
+		$SQL = '';
4765
+		foreach ($this->_tables as $table_obj) {
4766
+			if ($table_obj instanceof EE_Primary_Table) {
4767
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4768
+					? $table_obj->get_select_join_limit($limit)
4769
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4770
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4771
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4772
+					? $table_obj->get_select_join_limit_join($limit)
4773
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4774
+			}
4775
+		}
4776
+		return $SQL;
4777
+	}
4778
+
4779
+
4780
+	/**
4781
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4782
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4783
+	 *
4784
+	 * @return string SQL
4785
+	 * @throws EE_Error
4786
+	 */
4787
+	public function _construct_internal_join()
4788
+	{
4789
+		$SQL = $this->_get_main_table()->get_table_sql();
4790
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4791
+		return $SQL;
4792
+	}
4793
+
4794
+
4795
+	/**
4796
+	 * Constructs the SQL for joining all the tables on this model.
4797
+	 * Normally $alias should be the primary table's alias, but in cases where
4798
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4799
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4800
+	 * alias, this will construct SQL like:
4801
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4802
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4803
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4804
+	 *
4805
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4806
+	 * @return string
4807
+	 * @throws EE_Error
4808
+	 */
4809
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4810
+	{
4811
+		$SQL               = '';
4812
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4813
+		foreach ($this->_tables as $table_obj) {
4814
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4815
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4816
+					// so we're joining to this table, meaning the table is already in
4817
+					// the FROM statement, BUT the primary table isn't. So we want
4818
+					// to add the inverse join sql
4819
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4820
+				} else {
4821
+					// just add a regular JOIN to this table from the primary table
4822
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4823
+				}
4824
+			}//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4825
+		}
4826
+		return $SQL;
4827
+	}
4828
+
4829
+
4830
+	/**
4831
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4832
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4833
+	 * their data type (eg, '%s', '%d', etc)
4834
+	 *
4835
+	 * @return array
4836
+	 */
4837
+	public function _get_data_types()
4838
+	{
4839
+		$data_types = [];
4840
+		foreach ($this->field_settings() as $field_obj) {
4841
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4842
+			/** @var $field_obj EE_Model_Field_Base */
4843
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4844
+		}
4845
+		return $data_types;
4846
+	}
4847
+
4848
+
4849
+	/**
4850
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4851
+	 *
4852
+	 * @param string $model_name
4853
+	 * @return EEM_Base
4854
+	 * @throws EE_Error
4855
+	 */
4856
+	public function get_related_model_obj($model_name)
4857
+	{
4858
+		$model_classname = "EEM_" . $model_name;
4859
+		if (! class_exists($model_classname)) {
4860
+			throw new EE_Error(
4861
+				sprintf(
4862
+					esc_html__(
4863
+						"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4864
+						'event_espresso'
4865
+					),
4866
+					$model_name,
4867
+					$model_classname
4868
+				)
4869
+			);
4870
+		}
4871
+		return call_user_func($model_classname . "::instance");
4872
+	}
4873
+
4874
+
4875
+	/**
4876
+	 * Returns the array of EE_ModelRelations for this model.
4877
+	 *
4878
+	 * @return EE_Model_Relation_Base[]
4879
+	 */
4880
+	public function relation_settings()
4881
+	{
4882
+		return $this->_model_relations;
4883
+	}
4884
+
4885
+
4886
+	/**
4887
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4888
+	 * because without THOSE models, this model probably doesn't have much purpose.
4889
+	 * (Eg, without an event, datetimes have little purpose.)
4890
+	 *
4891
+	 * @return EE_Belongs_To_Relation[]
4892
+	 */
4893
+	public function belongs_to_relations()
4894
+	{
4895
+		$belongs_to_relations = [];
4896
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4897
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
4898
+				$belongs_to_relations[ $model_name ] = $relation_obj;
4899
+			}
4900
+		}
4901
+		return $belongs_to_relations;
4902
+	}
4903
+
4904
+
4905
+	/**
4906
+	 * Returns the specified EE_Model_Relation, or throws an exception
4907
+	 *
4908
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
4909
+	 * @return EE_Model_Relation_Base
4910
+	 * @throws EE_Error
4911
+	 */
4912
+	public function related_settings_for($relation_name)
4913
+	{
4914
+		$relatedModels = $this->relation_settings();
4915
+		if (! array_key_exists($relation_name, $relatedModels)) {
4916
+			throw new EE_Error(
4917
+				sprintf(
4918
+					esc_html__(
4919
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4920
+						'event_espresso'
4921
+					),
4922
+					$relation_name,
4923
+					$this->_get_class_name(),
4924
+					implode(', ', array_keys($relatedModels))
4925
+				)
4926
+			);
4927
+		}
4928
+		return $relatedModels[ $relation_name ];
4929
+	}
4930
+
4931
+
4932
+	/**
4933
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4934
+	 * fields
4935
+	 *
4936
+	 * @param string  $fieldName
4937
+	 * @param boolean $include_db_only_fields
4938
+	 * @return EE_Model_Field_Base
4939
+	 * @throws EE_Error
4940
+	 */
4941
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
4942
+	{
4943
+		$fieldSettings = $this->field_settings($include_db_only_fields);
4944
+		if (! array_key_exists($fieldName, $fieldSettings)) {
4945
+			throw new EE_Error(
4946
+				sprintf(
4947
+					esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4948
+					$fieldName,
4949
+					get_class($this)
4950
+				)
4951
+			);
4952
+		}
4953
+		return $fieldSettings[ $fieldName ];
4954
+	}
4955
+
4956
+
4957
+	/**
4958
+	 * Checks if this field exists on this model
4959
+	 *
4960
+	 * @param string $fieldName a key in the model's _field_settings array
4961
+	 * @return boolean
4962
+	 */
4963
+	public function has_field($fieldName)
4964
+	{
4965
+		$fieldSettings = $this->field_settings(true);
4966
+		if (isset($fieldSettings[ $fieldName ])) {
4967
+			return true;
4968
+		}
4969
+		return false;
4970
+	}
4971
+
4972
+
4973
+	/**
4974
+	 * Returns whether or not this model has a relation to the specified model
4975
+	 *
4976
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
4977
+	 * @return boolean
4978
+	 */
4979
+	public function has_relation($relation_name)
4980
+	{
4981
+		$relations = $this->relation_settings();
4982
+		if (isset($relations[ $relation_name ])) {
4983
+			return true;
4984
+		}
4985
+		return false;
4986
+	}
4987
+
4988
+
4989
+	/**
4990
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4991
+	 * Eg, on EE_Answer that would be ANS_ID field object
4992
+	 *
4993
+	 * @param $field_obj
4994
+	 * @return boolean
4995
+	 */
4996
+	public function is_primary_key_field($field_obj)
4997
+	{
4998
+		return $field_obj instanceof EE_Primary_Key_Field_Base;
4999
+	}
5000
+
5001
+
5002
+	/**
5003
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5004
+	 * Eg, on EE_Answer that would be ANS_ID field object
5005
+	 *
5006
+	 * @return EE_Model_Field_Base
5007
+	 * @throws EE_Error
5008
+	 */
5009
+	public function get_primary_key_field()
5010
+	{
5011
+		if ($this->_primary_key_field === null) {
5012
+			foreach ($this->field_settings(true) as $field_obj) {
5013
+				if ($this->is_primary_key_field($field_obj)) {
5014
+					$this->_primary_key_field = $field_obj;
5015
+					break;
5016
+				}
5017
+			}
5018
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5019
+				throw new EE_Error(
5020
+					sprintf(
5021
+						esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5022
+						get_class($this)
5023
+					)
5024
+				);
5025
+			}
5026
+		}
5027
+		return $this->_primary_key_field;
5028
+	}
5029
+
5030
+
5031
+	/**
5032
+	 * Returns whether or not not there is a primary key on this model.
5033
+	 * Internally does some caching.
5034
+	 *
5035
+	 * @return boolean
5036
+	 */
5037
+	public function has_primary_key_field()
5038
+	{
5039
+		if ($this->_has_primary_key_field === null) {
5040
+			try {
5041
+				$this->get_primary_key_field();
5042
+				$this->_has_primary_key_field = true;
5043
+			} catch (EE_Error $e) {
5044
+				$this->_has_primary_key_field = false;
5045
+			}
5046
+		}
5047
+		return $this->_has_primary_key_field;
5048
+	}
5049
+
5050
+
5051
+	/**
5052
+	 * Finds the first field of type $field_class_name.
5053
+	 *
5054
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5055
+	 *                                 EE_Foreign_Key_Field, etc
5056
+	 * @return EE_Model_Field_Base or null if none is found
5057
+	 */
5058
+	public function get_a_field_of_type($field_class_name)
5059
+	{
5060
+		foreach ($this->field_settings() as $field) {
5061
+			if ($field instanceof $field_class_name) {
5062
+				return $field;
5063
+			}
5064
+		}
5065
+		return null;
5066
+	}
5067
+
5068
+
5069
+	/**
5070
+	 * Gets a foreign key field pointing to model.
5071
+	 *
5072
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5073
+	 * @return EE_Foreign_Key_Field_Base
5074
+	 * @throws EE_Error
5075
+	 */
5076
+	public function get_foreign_key_to($model_name)
5077
+	{
5078
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5079
+			foreach ($this->field_settings() as $field) {
5080
+				if (
5081
+					$field instanceof EE_Foreign_Key_Field_Base
5082
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5083
+				) {
5084
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5085
+					break;
5086
+				}
5087
+			}
5088
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5089
+				throw new EE_Error(
5090
+					sprintf(
5091
+						esc_html__(
5092
+							"There is no foreign key field pointing to model %s on model %s",
5093
+							'event_espresso'
5094
+						),
5095
+						$model_name,
5096
+						get_class($this)
5097
+					)
5098
+				);
5099
+			}
5100
+		}
5101
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5102
+	}
5103
+
5104
+
5105
+	/**
5106
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5107
+	 *
5108
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5109
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5110
+	 *                            Either one works
5111
+	 * @return string
5112
+	 */
5113
+	public function get_table_for_alias($table_alias)
5114
+	{
5115
+		$table_alias_sans_model_relation_chain_prefix =
5116
+			EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5117
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5118
+	}
5119
+
5120
+
5121
+	/**
5122
+	 * Returns a flat array of all field son this model, instead of organizing them
5123
+	 * by table_alias as they are in the constructor.
5124
+	 *
5125
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5126
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5127
+	 */
5128
+	public function field_settings($include_db_only_fields = false)
5129
+	{
5130
+		if ($include_db_only_fields) {
5131
+			if ($this->_cached_fields === null) {
5132
+				$this->_cached_fields = [];
5133
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5134
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5135
+						$this->_cached_fields[ $field_name ] = $field_obj;
5136
+					}
5137
+				}
5138
+			}
5139
+			return $this->_cached_fields;
5140
+		}
5141
+		if ($this->_cached_fields_non_db_only === null) {
5142
+			$this->_cached_fields_non_db_only = [];
5143
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5144
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5145
+					/** @var $field_obj EE_Model_Field_Base */
5146
+					if (! $field_obj->is_db_only_field()) {
5147
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5148
+					}
5149
+				}
5150
+			}
5151
+		}
5152
+		return $this->_cached_fields_non_db_only;
5153
+	}
5154
+
5155
+
5156
+	/**
5157
+	 *        cycle though array of attendees and create objects out of each item
5158
+	 *
5159
+	 * @access        private
5160
+	 * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5161
+	 * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5162
+	 *                           numerically indexed)
5163
+	 * @throws EE_Error
5164
+	 * @throws ReflectionException
5165
+	 */
5166
+	protected function _create_objects($rows = [])
5167
+	{
5168
+		$array_of_objects = [];
5169
+		if (empty($rows)) {
5170
+			return [];
5171
+		}
5172
+		$count_if_model_has_no_primary_key = 0;
5173
+		$has_primary_key                   = $this->has_primary_key_field();
5174
+		$primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5175
+		foreach ((array)$rows as $row) {
5176
+			if (empty($row)) {
5177
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5178
+				return [];
5179
+			}
5180
+			// check if we've already set this object in the results array,
5181
+			// in which case there's no need to process it further (again)
5182
+			if ($has_primary_key) {
5183
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5184
+					$row,
5185
+					$primary_key_field->get_qualified_column(),
5186
+					$primary_key_field->get_table_column()
5187
+				);
5188
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5189
+					continue;
5190
+				}
5191
+			}
5192
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5193
+			if (! $classInstance) {
5194
+				throw new EE_Error(
5195
+					sprintf(
5196
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5197
+						$this->get_this_model_name(),
5198
+						http_build_query($row)
5199
+					)
5200
+				);
5201
+			}
5202
+			// set the timezone on the instantiated objects
5203
+			$classInstance->set_timezone($this->_timezone);
5204
+			// make sure if there is any timezone setting present that we set the timezone for the object
5205
+			$key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5206
+			$array_of_objects[ $key ] = $classInstance;
5207
+			// also, for all the relations of type BelongsTo, see if we can cache
5208
+			// those related models
5209
+			// (we could do this for other relations too, but if there are conditions
5210
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5211
+			// so it requires a little more thought than just caching them immediately...)
5212
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5213
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5214
+					// check if this model's INFO is present. If so, cache it on the model
5215
+					$other_model           = $relation_obj->get_other_model();
5216
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5217
+					// if we managed to make a model object from the results, cache it on the main model object
5218
+					if ($other_model_obj_maybe) {
5219
+						// set timezone on these other model objects if they are present
5220
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5221
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5222
+					}
5223
+				}
5224
+			}
5225
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5226
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5227
+			// the field in the CustomSelects object
5228
+			if ($this->_custom_selections instanceof CustomSelects) {
5229
+				$classInstance->setCustomSelectsValues(
5230
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5231
+				);
5232
+			}
5233
+		}
5234
+		return $array_of_objects;
5235
+	}
5236
+
5237
+
5238
+	/**
5239
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5240
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5241
+	 *
5242
+	 * @param array $db_results_row
5243
+	 * @return array
5244
+	 */
5245
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5246
+	{
5247
+		$results = [];
5248
+		if ($this->_custom_selections instanceof CustomSelects) {
5249
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5250
+				if (isset($db_results_row[ $alias ])) {
5251
+					$results[ $alias ] = $this->convertValueToDataType(
5252
+						$db_results_row[ $alias ],
5253
+						$this->_custom_selections->getDataTypeForAlias($alias)
5254
+					);
5255
+				}
5256
+			}
5257
+		}
5258
+		return $results;
5259
+	}
5260
+
5261
+
5262
+	/**
5263
+	 * This will set the value for the given alias
5264
+	 *
5265
+	 * @param string $value
5266
+	 * @param string $datatype (one of %d, %s, %f)
5267
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5268
+	 */
5269
+	protected function convertValueToDataType($value, $datatype)
5270
+	{
5271
+		switch ($datatype) {
5272
+			case '%f':
5273
+				return (float)$value;
5274
+			case '%d':
5275
+				return (int)$value;
5276
+			default:
5277
+				return (string)$value;
5278
+		}
5279
+	}
5280
+
5281
+
5282
+	/**
5283
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5284
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5285
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5286
+	 * object (as set in the model_field!).
5287
+	 *
5288
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5289
+	 * @throws EE_Error
5290
+	 * @throws ReflectionException
5291
+	 */
5292
+	public function create_default_object()
5293
+	{
5294
+		$this_model_fields_and_values = [];
5295
+		// setup the row using default values;
5296
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5297
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5298
+		}
5299
+		$className = $this->_get_class_name();
5300
+		return EE_Registry::instance()->load_class(
5301
+			$className,
5302
+			[$this_model_fields_and_values],
5303
+			false,
5304
+			false
5305
+		);
5306
+	}
5307
+
5308
+
5309
+	/**
5310
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5311
+	 *                             or an stdClass where each property is the name of a column,
5312
+	 * @return EE_Base_Class
5313
+	 * @throws EE_Error
5314
+	 * @throws ReflectionException
5315
+	 */
5316
+	public function instantiate_class_from_array_or_object($cols_n_values)
5317
+	{
5318
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5319
+			$cols_n_values = get_object_vars($cols_n_values);
5320
+		}
5321
+		$primary_key = null;
5322
+		// make sure the array only has keys that are fields/columns on this model
5323
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5324
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5325
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5326
+		}
5327
+		$className = $this->_get_class_name();
5328
+		// check we actually found results that we can use to build our model object
5329
+		// if not, return null
5330
+		if ($this->has_primary_key_field()) {
5331
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5332
+				return null;
5333
+			}
5334
+		} elseif ($this->unique_indexes()) {
5335
+			$first_column = reset($this_model_fields_n_values);
5336
+			if (empty($first_column)) {
5337
+				return null;
5338
+			}
5339
+		}
5340
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5341
+		if ($primary_key) {
5342
+			$classInstance = $this->get_from_entity_map($primary_key);
5343
+			if (! $classInstance) {
5344
+				$classInstance = EE_Registry::instance()->load_class(
5345
+					$className,
5346
+					[$this_model_fields_n_values, $this->_timezone],
5347
+					true,
5348
+					false
5349
+				);
5350
+				// add this new object to the entity map
5351
+				$classInstance = $this->add_to_entity_map($classInstance);
5352
+			}
5353
+		} else {
5354
+			$classInstance = EE_Registry::instance()->load_class(
5355
+				$className,
5356
+				[$this_model_fields_n_values, $this->_timezone],
5357
+				true,
5358
+				false
5359
+			);
5360
+		}
5361
+		return $classInstance;
5362
+	}
5363
+
5364
+
5365
+	/**
5366
+	 * Gets the model object from the  entity map if it exists
5367
+	 *
5368
+	 * @param int|string $id the ID of the model object
5369
+	 * @return EE_Base_Class
5370
+	 */
5371
+	public function get_from_entity_map($id)
5372
+	{
5373
+		return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5374
+			? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5375
+	}
5376
+
5377
+
5378
+	/**
5379
+	 * add_to_entity_map
5380
+	 * Adds the object to the model's entity mappings
5381
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5382
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5383
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5384
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5385
+	 *        then this method should be called immediately after the update query
5386
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5387
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5388
+	 *
5389
+	 * @param EE_Base_Class $object
5390
+	 * @return EE_Base_Class
5391
+	 * @throws EE_Error
5392
+	 * @throws ReflectionException
5393
+	 */
5394
+	public function add_to_entity_map(EE_Base_Class $object)
5395
+	{
5396
+		$className = $this->_get_class_name();
5397
+		if (! $object instanceof $className) {
5398
+			throw new EE_Error(
5399
+				sprintf(
5400
+					esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5401
+					is_object($object) ? get_class($object) : $object,
5402
+					$className
5403
+				)
5404
+			);
5405
+		}
5406
+		if (! $object->ID()) {
5407
+			throw new EE_Error(
5408
+				sprintf(
5409
+					esc_html__(
5410
+						"You tried storing a model object with NO ID in the %s entity mapper.",
5411
+						"event_espresso"
5412
+					),
5413
+					get_class($this)
5414
+				)
5415
+			);
5416
+		}
5417
+		// double check it's not already there
5418
+		$classInstance = $this->get_from_entity_map($object->ID());
5419
+		if ($classInstance) {
5420
+			return $classInstance;
5421
+		}
5422
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5423
+		return $object;
5424
+	}
5425
+
5426
+
5427
+	/**
5428
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5429
+	 * if no identifier is provided, then the entire entity map is emptied
5430
+	 *
5431
+	 * @param int|string $id the ID of the model object
5432
+	 * @return boolean
5433
+	 */
5434
+	public function clear_entity_map($id = null)
5435
+	{
5436
+		if (empty($id)) {
5437
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5438
+			return true;
5439
+		}
5440
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5441
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5442
+			return true;
5443
+		}
5444
+		return false;
5445
+	}
5446
+
5447
+
5448
+	/**
5449
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5450
+	 * Given an array where keys are column (or column alias) names and values,
5451
+	 * returns an array of their corresponding field names and database values
5452
+	 *
5453
+	 * @param array $cols_n_values
5454
+	 * @return array
5455
+	 */
5456
+	public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5457
+	{
5458
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5459
+	}
5460
+
5461
+
5462
+	/**
5463
+	 * _deduce_fields_n_values_from_cols_n_values
5464
+	 * Given an array where keys are column (or column alias) names and values,
5465
+	 * returns an array of their corresponding field names and database values
5466
+	 *
5467
+	 * @param array $cols_n_values
5468
+	 * @return array
5469
+	 */
5470
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5471
+	{
5472
+		$this_model_fields_n_values = [];
5473
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5474
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5475
+				$cols_n_values,
5476
+				$table_obj->get_fully_qualified_pk_column(),
5477
+				$table_obj->get_pk_column()
5478
+			);
5479
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5480
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5481
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5482
+					if (! $field_obj->is_db_only_field()) {
5483
+						// prepare field as if its coming from db
5484
+						$prepared_value                            =
5485
+							$field_obj->prepare_for_set($field_obj->get_default_value());
5486
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5487
+					}
5488
+				}
5489
+			} else {
5490
+				// the table's rows existed. Use their values
5491
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5492
+					if (! $field_obj->is_db_only_field()) {
5493
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5494
+							$cols_n_values,
5495
+							$field_obj->get_qualified_column(),
5496
+							$field_obj->get_table_column()
5497
+						);
5498
+					}
5499
+				}
5500
+			}
5501
+		}
5502
+		return $this_model_fields_n_values;
5503
+	}
5504
+
5505
+
5506
+	/**
5507
+	 * @param array  $cols_n_values
5508
+	 * @param string $qualified_column
5509
+	 * @param string $regular_column
5510
+	 * @return null
5511
+	 */
5512
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5513
+	{
5514
+		$value = null;
5515
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5516
+		// does the field on the model relate to this column retrieved from the db?
5517
+		// or is it a db-only field? (not relating to the model)
5518
+		if (isset($cols_n_values[ $qualified_column ])) {
5519
+			$value = $cols_n_values[ $qualified_column ];
5520
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5521
+			$value = $cols_n_values[ $regular_column ];
5522
+		} elseif (! empty($this->foreign_key_aliases)) {
5523
+			// no PK?  ok check if there is a foreign key alias set for this table
5524
+			// then check if that alias exists in the incoming data
5525
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5526
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5527
+				if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5528
+					$value = $cols_n_values[ $FK_alias ];
5529
+					break;
5530
+				}
5531
+			}
5532
+		}
5533
+		return $value;
5534
+	}
5535
+
5536
+
5537
+	/**
5538
+	 * refresh_entity_map_from_db
5539
+	 * Makes sure the model object in the entity map at $id assumes the values
5540
+	 * of the database (opposite of EE_base_Class::save())
5541
+	 *
5542
+	 * @param int|string $id
5543
+	 * @return EE_Base_Class
5544
+	 * @throws EE_Error
5545
+	 * @throws ReflectionException
5546
+	 */
5547
+	public function refresh_entity_map_from_db($id)
5548
+	{
5549
+		$obj_in_map = $this->get_from_entity_map($id);
5550
+		if ($obj_in_map) {
5551
+			$wpdb_results = $this->_get_all_wpdb_results(
5552
+				[[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5553
+			);
5554
+			if ($wpdb_results && is_array($wpdb_results)) {
5555
+				$one_row = reset($wpdb_results);
5556
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5557
+					$obj_in_map->set_from_db($field_name, $db_value);
5558
+				}
5559
+				// clear the cache of related model objects
5560
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5561
+					$obj_in_map->clear_cache($relation_name, null, true);
5562
+				}
5563
+			}
5564
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5565
+			return $obj_in_map;
5566
+		}
5567
+		return $this->get_one_by_ID($id);
5568
+	}
5569
+
5570
+
5571
+	/**
5572
+	 * refresh_entity_map_with
5573
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5574
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5575
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5576
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5577
+	 *
5578
+	 * @param int|string    $id
5579
+	 * @param EE_Base_Class $replacing_model_obj
5580
+	 * @return EE_Base_Class
5581
+	 * @throws EE_Error
5582
+	 * @throws ReflectionException
5583
+	 */
5584
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5585
+	{
5586
+		$obj_in_map = $this->get_from_entity_map($id);
5587
+		if ($obj_in_map) {
5588
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5589
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5590
+					$obj_in_map->set($field_name, $value);
5591
+				}
5592
+				// make the model object in the entity map's cache match the $replacing_model_obj
5593
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5594
+					$obj_in_map->clear_cache($relation_name, null, true);
5595
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5596
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5597
+					}
5598
+				}
5599
+			}
5600
+			return $obj_in_map;
5601
+		}
5602
+		$this->add_to_entity_map($replacing_model_obj);
5603
+		return $replacing_model_obj;
5604
+	}
5605
+
5606
+
5607
+	/**
5608
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5609
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5610
+	 * require_once($this->_getClassName().".class.php");
5611
+	 *
5612
+	 * @return string
5613
+	 */
5614
+	private function _get_class_name()
5615
+	{
5616
+		return "EE_" . $this->get_this_model_name();
5617
+	}
5618
+
5619
+
5620
+	/**
5621
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5622
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5623
+	 * it would be 'Events'.
5624
+	 *
5625
+	 * @param int $quantity
5626
+	 * @return string
5627
+	 */
5628
+	public function item_name($quantity = 1)
5629
+	{
5630
+		return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5631
+	}
5632
+
5633
+
5634
+	/**
5635
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5636
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5637
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5638
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5639
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5640
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5641
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5642
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5643
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5644
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5645
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5646
+	 *        return $previousReturnValue.$returnString;
5647
+	 * }
5648
+	 * require('EEM_Answer.model.php');
5649
+	 * $answer=EEM_Answer::instance();
5650
+	 * echo $answer->my_callback('monkeys',100);
5651
+	 * //will output "you called my_callback! and passed args:monkeys,100"
5652
+	 *
5653
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5654
+	 * @param array  $args       array of original arguments passed to the function
5655
+	 * @return mixed whatever the plugin which calls add_filter decides
5656
+	 * @throws EE_Error
5657
+	 */
5658
+	public function __call($methodName, $args)
5659
+	{
5660
+		$className = get_class($this);
5661
+		$tagName   = "FHEE__{$className}__{$methodName}";
5662
+		if (! has_filter($tagName)) {
5663
+			throw new EE_Error(
5664
+				sprintf(
5665
+					esc_html__(
5666
+						'Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5667
+						'event_espresso'
5668
+					),
5669
+					$methodName,
5670
+					$className,
5671
+					$tagName,
5672
+					'<br />'
5673
+				)
5674
+			);
5675
+		}
5676
+		return apply_filters($tagName, null, $this, $args);
5677
+	}
5678
+
5679
+
5680
+	/**
5681
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5682
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5683
+	 *
5684
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5685
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5686
+	 *                                                       the object's class name
5687
+	 *                                                       or object's ID
5688
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5689
+	 *                                                       exists in the database. If it does not, we add it
5690
+	 * @return EE_Base_Class
5691
+	 * @throws EE_Error
5692
+	 * @throws ReflectionException
5693
+	 */
5694
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5695
+	{
5696
+		$className = $this->_get_class_name();
5697
+		if ($base_class_obj_or_id instanceof $className) {
5698
+			$model_object = $base_class_obj_or_id;
5699
+		} else {
5700
+			$primary_key_field = $this->get_primary_key_field();
5701
+			if (
5702
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5703
+				&& (
5704
+					is_int($base_class_obj_or_id)
5705
+					|| is_string($base_class_obj_or_id)
5706
+				)
5707
+			) {
5708
+				// assume it's an ID.
5709
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5710
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5711
+			} elseif (
5712
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5713
+				&& is_string($base_class_obj_or_id)
5714
+			) {
5715
+				// assume its a string representation of the object
5716
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5717
+			} else {
5718
+				throw new EE_Error(
5719
+					sprintf(
5720
+						esc_html__(
5721
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5722
+							'event_espresso'
5723
+						),
5724
+						$base_class_obj_or_id,
5725
+						$this->_get_class_name(),
5726
+						print_r($base_class_obj_or_id, true)
5727
+					)
5728
+				);
5729
+			}
5730
+		}
5731
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5732
+			$model_object->save();
5733
+		}
5734
+		return $model_object;
5735
+	}
5736
+
5737
+
5738
+	/**
5739
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5740
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5741
+	 * returns it ID.
5742
+	 *
5743
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5744
+	 * @return int|string depending on the type of this model object's ID
5745
+	 * @throws EE_Error
5746
+	 * @throws ReflectionException
5747
+	 */
5748
+	public function ensure_is_ID($base_class_obj_or_id)
5749
+	{
5750
+		$className = $this->_get_class_name();
5751
+		if ($base_class_obj_or_id instanceof $className) {
5752
+			$id = $base_class_obj_or_id->ID();
5753
+		} elseif (is_int($base_class_obj_or_id)) {
5754
+			// assume it's an ID
5755
+			$id = $base_class_obj_or_id;
5756
+		} elseif (is_string($base_class_obj_or_id)) {
5757
+			// assume its a string representation of the object
5758
+			$id = $base_class_obj_or_id;
5759
+		} else {
5760
+			throw new EE_Error(
5761
+				sprintf(
5762
+					esc_html__(
5763
+						"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5764
+						'event_espresso'
5765
+					),
5766
+					$base_class_obj_or_id,
5767
+					$this->_get_class_name(),
5768
+					print_r($base_class_obj_or_id, true)
5769
+				)
5770
+			);
5771
+		}
5772
+		return $id;
5773
+	}
5774
+
5775
+
5776
+	/**
5777
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5778
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5779
+	 * been sanitized and converted into the appropriate domain.
5780
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5781
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5782
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5783
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5784
+	 * $EVT = EEM_Event::instance(); $old_setting =
5785
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5786
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5787
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5788
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5789
+	 *
5790
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5791
+	 * @return void
5792
+	 */
5793
+	public function assume_values_already_prepared_by_model_object(
5794
+		$values_already_prepared = self::not_prepared_by_model_object
5795
+	) {
5796
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5797
+	}
5798
+
5799
+
5800
+	/**
5801
+	 * Read comments for assume_values_already_prepared_by_model_object()
5802
+	 *
5803
+	 * @return int
5804
+	 */
5805
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5806
+	{
5807
+		return $this->_values_already_prepared_by_model_object;
5808
+	}
5809
+
5810
+
5811
+	/**
5812
+	 * Gets all the indexes on this model
5813
+	 *
5814
+	 * @return EE_Index[]
5815
+	 */
5816
+	public function indexes()
5817
+	{
5818
+		return $this->_indexes;
5819
+	}
5820
+
5821
+
5822
+	/**
5823
+	 * Gets all the Unique Indexes on this model
5824
+	 *
5825
+	 * @return EE_Unique_Index[]
5826
+	 */
5827
+	public function unique_indexes()
5828
+	{
5829
+		$unique_indexes = [];
5830
+		foreach ($this->_indexes as $name => $index) {
5831
+			if ($index instanceof EE_Unique_Index) {
5832
+				$unique_indexes [ $name ] = $index;
5833
+			}
5834
+		}
5835
+		return $unique_indexes;
5836
+	}
5837
+
5838
+
5839
+	/**
5840
+	 * Gets all the fields which, when combined, make the primary key.
5841
+	 * This is usually just an array with 1 element (the primary key), but in cases
5842
+	 * where there is no primary key, it's a combination of fields as defined
5843
+	 * on a primary index
5844
+	 *
5845
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5846
+	 * @throws EE_Error
5847
+	 */
5848
+	public function get_combined_primary_key_fields()
5849
+	{
5850
+		foreach ($this->indexes() as $index) {
5851
+			if ($index instanceof EE_Primary_Key_Index) {
5852
+				return $index->fields();
5853
+			}
5854
+		}
5855
+		return [$this->primary_key_name() => $this->get_primary_key_field()];
5856
+	}
5857
+
5858
+
5859
+	/**
5860
+	 * Used to build a primary key string (when the model has no primary key),
5861
+	 * which can be used a unique string to identify this model object.
5862
+	 *
5863
+	 * @param array $fields_n_values keys are field names, values are their values.
5864
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5865
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5866
+	 *                               before passing it to this function (that will convert it from columns-n-values
5867
+	 *                               to field-names-n-values).
5868
+	 * @return string
5869
+	 * @throws EE_Error
5870
+	 */
5871
+	public function get_index_primary_key_string($fields_n_values)
5872
+	{
5873
+		$cols_n_values_for_primary_key_index = array_intersect_key(
5874
+			$fields_n_values,
5875
+			$this->get_combined_primary_key_fields()
5876
+		);
5877
+		return http_build_query($cols_n_values_for_primary_key_index);
5878
+	}
5879
+
5880
+
5881
+	/**
5882
+	 * Gets the field values from the primary key string
5883
+	 *
5884
+	 * @param string $index_primary_key_string
5885
+	 * @return null|array
5886
+	 * @throws EE_Error
5887
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5888
+	 */
5889
+	public function parse_index_primary_key_string($index_primary_key_string)
5890
+	{
5891
+		$key_fields = $this->get_combined_primary_key_fields();
5892
+		// check all of them are in the $id
5893
+		$key_values_in_combined_pk = [];
5894
+		parse_str($index_primary_key_string, $key_values_in_combined_pk);
5895
+		foreach ($key_fields as $key_field_name => $field_obj) {
5896
+			if (! isset($key_values_in_combined_pk[ $key_field_name ])) {
5897
+				return null;
5898
+			}
5899
+		}
5900
+		return $key_values_in_combined_pk;
5901
+	}
5902
+
5903
+
5904
+	/**
5905
+	 * verifies that an array of key-value pairs for model fields has a key
5906
+	 * for each field comprising the primary key index
5907
+	 *
5908
+	 * @param array $key_values
5909
+	 * @return boolean
5910
+	 * @throws EE_Error
5911
+	 */
5912
+	public function has_all_combined_primary_key_fields($key_values)
5913
+	{
5914
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5915
+		foreach ($keys_it_should_have as $key) {
5916
+			if (! isset($key_values[ $key ])) {
5917
+				return false;
5918
+			}
5919
+		}
5920
+		return true;
5921
+	}
5922
+
5923
+
5924
+	/**
5925
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5926
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
5927
+	 *
5928
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5929
+	 * @param array               $query_params                     see github link below for more info
5930
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5931
+	 * @throws EE_Error
5932
+	 * @throws ReflectionException
5933
+	 * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5934
+	 *                                                              indexed)
5935
+	 */
5936
+	public function get_all_copies($model_object_or_attributes_array, $query_params = [])
5937
+	{
5938
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5939
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
5940
+		} elseif (is_array($model_object_or_attributes_array)) {
5941
+			$attributes_array = $model_object_or_attributes_array;
5942
+		} else {
5943
+			throw new EE_Error(
5944
+				sprintf(
5945
+					esc_html__(
5946
+						"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5947
+						"event_espresso"
5948
+					),
5949
+					$model_object_or_attributes_array
5950
+				)
5951
+			);
5952
+		}
5953
+		// even copies obviously won't have the same ID, so remove the primary key
5954
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
5955
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5956
+			unset($attributes_array[ $this->primary_key_name() ]);
5957
+		}
5958
+		if (isset($query_params[0])) {
5959
+			$query_params[0] = array_merge($attributes_array, $query_params);
5960
+		} else {
5961
+			$query_params[0] = $attributes_array;
5962
+		}
5963
+		return $this->get_all($query_params);
5964
+	}
5965
+
5966
+
5967
+	/**
5968
+	 * Gets the first copy we find. See get_all_copies for more details
5969
+	 *
5970
+	 * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
5971
+	 * @param array $query_params
5972
+	 * @return EE_Base_Class
5973
+	 * @throws EE_Error
5974
+	 * @throws ReflectionException
5975
+	 */
5976
+	public function get_one_copy($model_object_or_attributes_array, $query_params = [])
5977
+	{
5978
+		if (! is_array($query_params)) {
5979
+			EE_Error::doing_it_wrong(
5980
+				'EEM_Base::get_one_copy',
5981
+				sprintf(
5982
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5983
+					gettype($query_params)
5984
+				),
5985
+				'4.6.0'
5986
+			);
5987
+			$query_params = [];
5988
+		}
5989
+		$query_params['limit'] = 1;
5990
+		$copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5991
+		if (is_array($copies)) {
5992
+			return array_shift($copies);
5993
+		}
5994
+		return null;
5995
+	}
5996
+
5997
+
5998
+	/**
5999
+	 * Updates the item with the specified id. Ignores default query parameters because
6000
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6001
+	 *
6002
+	 * @param array      $fields_n_values keys are field names, values are their new values
6003
+	 * @param int|string $id              the value of the primary key to update
6004
+	 * @return int number of rows updated
6005
+	 * @throws EE_Error
6006
+	 * @throws ReflectionException
6007
+	 */
6008
+	public function update_by_ID($fields_n_values, $id)
6009
+	{
6010
+		$query_params = [
6011
+			0                          => [$this->get_primary_key_field()->get_name() => $id],
6012
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6013
+		];
6014
+		return $this->update($fields_n_values, $query_params);
6015
+	}
6016
+
6017
+
6018
+	/**
6019
+	 * Changes an operator which was supplied to the models into one usable in SQL
6020
+	 *
6021
+	 * @param string $operator_supplied
6022
+	 * @return string an operator which can be used in SQL
6023
+	 * @throws EE_Error
6024
+	 */
6025
+	private function _prepare_operator_for_sql($operator_supplied)
6026
+	{
6027
+		$sql_operator =
6028
+			isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6029
+				: null;
6030
+		if ($sql_operator) {
6031
+			return $sql_operator;
6032
+		}
6033
+		throw new EE_Error(
6034
+			sprintf(
6035
+				esc_html__(
6036
+					"The operator '%s' is not in the list of valid operators: %s",
6037
+					"event_espresso"
6038
+				),
6039
+				$operator_supplied,
6040
+				implode(",", array_keys($this->_valid_operators))
6041
+			)
6042
+		);
6043
+	}
6044
+
6045
+
6046
+	/**
6047
+	 * Gets the valid operators
6048
+	 *
6049
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6050
+	 */
6051
+	public function valid_operators()
6052
+	{
6053
+		return $this->_valid_operators;
6054
+	}
6055
+
6056
+
6057
+	/**
6058
+	 * Gets the between-style operators (take 2 arguments).
6059
+	 *
6060
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6061
+	 */
6062
+	public function valid_between_style_operators()
6063
+	{
6064
+		return array_intersect(
6065
+			$this->valid_operators(),
6066
+			$this->_between_style_operators
6067
+		);
6068
+	}
6069
+
6070
+
6071
+	/**
6072
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6073
+	 *
6074
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6075
+	 */
6076
+	public function valid_like_style_operators()
6077
+	{
6078
+		return array_intersect(
6079
+			$this->valid_operators(),
6080
+			$this->_like_style_operators
6081
+		);
6082
+	}
6083
+
6084
+
6085
+	/**
6086
+	 * Gets the "in"-style operators
6087
+	 *
6088
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6089
+	 */
6090
+	public function valid_in_style_operators()
6091
+	{
6092
+		return array_intersect(
6093
+			$this->valid_operators(),
6094
+			$this->_in_style_operators
6095
+		);
6096
+	}
6097
+
6098
+
6099
+	/**
6100
+	 * Gets the "null"-style operators (accept no arguments)
6101
+	 *
6102
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6103
+	 */
6104
+	public function valid_null_style_operators()
6105
+	{
6106
+		return array_intersect(
6107
+			$this->valid_operators(),
6108
+			$this->_null_style_operators
6109
+		);
6110
+	}
6111
+
6112
+
6113
+	/**
6114
+	 * Gets an array where keys are the primary keys and values are their 'names'
6115
+	 * (as determined by the model object's name() function, which is often overridden)
6116
+	 *
6117
+	 * @param array $query_params like get_all's
6118
+	 * @return string[]
6119
+	 * @throws EE_Error
6120
+	 * @throws ReflectionException
6121
+	 */
6122
+	public function get_all_names($query_params = [])
6123
+	{
6124
+		$objs  = $this->get_all($query_params);
6125
+		$names = [];
6126
+		foreach ($objs as $obj) {
6127
+			$names[ $obj->ID() ] = $obj->name();
6128
+		}
6129
+		return $names;
6130
+	}
6131
+
6132
+
6133
+	/**
6134
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6135
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6136
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6137
+	 * array_keys() on $model_objects.
6138
+	 *
6139
+	 * @param EE_Base_Class[] $model_objects
6140
+	 * @param boolean         $filter_out_empty_ids  if a model object has an ID of '' or 0, don't bother including it
6141
+	 *                                               in the returned array
6142
+	 * @return array
6143
+	 * @throws EE_Error
6144
+	 * @throws ReflectionException
6145
+	 */
6146
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6147
+	{
6148
+		if (! $this->has_primary_key_field()) {
6149
+			if (WP_DEBUG) {
6150
+				EE_Error::add_error(
6151
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6152
+					__FILE__,
6153
+					__FUNCTION__,
6154
+					__LINE__
6155
+				);
6156
+			}
6157
+		}
6158
+		$IDs = [];
6159
+		foreach ($model_objects as $model_object) {
6160
+			$id = $model_object->ID();
6161
+			if (! $id) {
6162
+				if ($filter_out_empty_ids) {
6163
+					continue;
6164
+				}
6165
+				if (WP_DEBUG) {
6166
+					EE_Error::add_error(
6167
+						esc_html__(
6168
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6169
+							'event_espresso'
6170
+						),
6171
+						__FILE__,
6172
+						__FUNCTION__,
6173
+						__LINE__
6174
+					);
6175
+				}
6176
+			}
6177
+			$IDs[] = $id;
6178
+		}
6179
+		return $IDs;
6180
+	}
6181
+
6182
+
6183
+	/**
6184
+	 * Returns the string used in capabilities relating to this model. If there
6185
+	 * are no capabilities that relate to this model returns false
6186
+	 *
6187
+	 * @return string|false
6188
+	 */
6189
+	public function cap_slug()
6190
+	{
6191
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6192
+	}
6193
+
6194
+
6195
+	/**
6196
+	 * Returns the capability-restrictions array (@param string $context
6197
+	 *
6198
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6199
+	 * @throws EE_Error
6200
+	 * @see EEM_Base::_cap_restrictions).
6201
+	 *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6202
+	 *      only returns the cap restrictions array in that context (ie, the array
6203
+	 *      at that key)
6204
+	 *
6205
+	 */
6206
+	public function cap_restrictions($context = EEM_Base::caps_read)
6207
+	{
6208
+		EEM_Base::verify_is_valid_cap_context($context);
6209
+		// check if we ought to run the restriction generator first
6210
+		if (
6211
+			isset($this->_cap_restriction_generators[ $context ])
6212
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6213
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6214
+		) {
6215
+			$this->_cap_restrictions[ $context ] = array_merge(
6216
+				$this->_cap_restrictions[ $context ],
6217
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6218
+			);
6219
+		}
6220
+		// and make sure we've finalized the construction of each restriction
6221
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6222
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6223
+				$where_conditions_obj->_finalize_construct($this);
6224
+			}
6225
+		}
6226
+		return $this->_cap_restrictions[ $context ];
6227
+	}
6228
+
6229
+
6230
+	/**
6231
+	 * Indicating whether or not this model thinks its a wp core model
6232
+	 *
6233
+	 * @return boolean
6234
+	 */
6235
+	public function is_wp_core_model()
6236
+	{
6237
+		return $this->_wp_core_model;
6238
+	}
6239
+
6240
+
6241
+	/**
6242
+	 * Gets all the caps that are missing which impose a restriction on
6243
+	 * queries made in this context
6244
+	 *
6245
+	 * @param string $context one of EEM_Base::caps_ constants
6246
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6247
+	 * @throws EE_Error
6248
+	 */
6249
+	public function caps_missing($context = EEM_Base::caps_read)
6250
+	{
6251
+		$missing_caps     = [];
6252
+		$cap_restrictions = $this->cap_restrictions($context);
6253
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6254
+			if (
6255
+			! EE_Capabilities::instance()
6256
+							 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6257
+			) {
6258
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6259
+			}
6260
+		}
6261
+		return $missing_caps;
6262
+	}
6263
+
6264
+
6265
+	/**
6266
+	 * Gets the mapping from capability contexts to action strings used in capability names
6267
+	 *
6268
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6269
+	 * one of 'read', 'edit', or 'delete'
6270
+	 */
6271
+	public function cap_contexts_to_cap_action_map()
6272
+	{
6273
+		return apply_filters(
6274
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6275
+			$this->_cap_contexts_to_cap_action_map,
6276
+			$this
6277
+		);
6278
+	}
6279
+
6280
+
6281
+	/**
6282
+	 * Gets the action string for the specified capability context
6283
+	 *
6284
+	 * @param string $context
6285
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6286
+	 * @throws EE_Error
6287
+	 */
6288
+	public function cap_action_for_context($context)
6289
+	{
6290
+		$mapping = $this->cap_contexts_to_cap_action_map();
6291
+		if (isset($mapping[ $context ])) {
6292
+			return $mapping[ $context ];
6293
+		}
6294
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6295
+			return $action;
6296
+		}
6297
+		throw new EE_Error(
6298
+			sprintf(
6299
+				esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6300
+				$context,
6301
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6302
+			)
6303
+		);
6304
+	}
6305
+
6306
+
6307
+	/**
6308
+	 * Returns all the capability contexts which are valid when querying models
6309
+	 *
6310
+	 * @return array
6311
+	 */
6312
+	public static function valid_cap_contexts()
6313
+	{
6314
+		return apply_filters(
6315
+			'FHEE__EEM_Base__valid_cap_contexts',
6316
+			[
6317
+				self::caps_read,
6318
+				self::caps_read_admin,
6319
+				self::caps_edit,
6320
+				self::caps_delete,
6321
+			]
6322
+		);
6323
+	}
6324
+
6325
+
6326
+	/**
6327
+	 * Returns all valid options for 'default_where_conditions'
6328
+	 *
6329
+	 * @return array
6330
+	 */
6331
+	public static function valid_default_where_conditions()
6332
+	{
6333
+		return [
6334
+			EEM_Base::default_where_conditions_all,
6335
+			EEM_Base::default_where_conditions_this_only,
6336
+			EEM_Base::default_where_conditions_others_only,
6337
+			EEM_Base::default_where_conditions_minimum_all,
6338
+			EEM_Base::default_where_conditions_minimum_others,
6339
+			EEM_Base::default_where_conditions_none,
6340
+		];
6341
+	}
6342
+
6343
+	// public static function default_where_conditions_full
6344
+
6345
+
6346
+	/**
6347
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6348
+	 *
6349
+	 * @param string $context
6350
+	 * @return bool
6351
+	 * @throws EE_Error
6352
+	 */
6353
+	public static function verify_is_valid_cap_context($context)
6354
+	{
6355
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6356
+		if (in_array($context, $valid_cap_contexts)) {
6357
+			return true;
6358
+		}
6359
+		throw new EE_Error(
6360
+			sprintf(
6361
+				esc_html__(
6362
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6363
+					'event_espresso'
6364
+				),
6365
+				$context,
6366
+				'EEM_Base',
6367
+				implode(',', $valid_cap_contexts)
6368
+			)
6369
+		);
6370
+	}
6371
+
6372
+
6373
+	/**
6374
+	 * Clears all the models field caches. This is only useful when a sub-class
6375
+	 * might have added a field or something and these caches might be invalidated
6376
+	 */
6377
+	protected function _invalidate_field_caches()
6378
+	{
6379
+		$this->_cache_foreign_key_to_fields = [];
6380
+		$this->_cached_fields               = null;
6381
+		$this->_cached_fields_non_db_only   = null;
6382
+	}
6383
+
6384
+
6385
+	/**
6386
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6387
+	 * (eg "and", "or", "not").
6388
+	 *
6389
+	 * @return array
6390
+	 */
6391
+	public function logic_query_param_keys()
6392
+	{
6393
+		return $this->_logic_query_param_keys;
6394
+	}
6395
+
6396
+
6397
+	/**
6398
+	 * Determines whether or not the where query param array key is for a logic query param.
6399
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6400
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6401
+	 *
6402
+	 * @param $query_param_key
6403
+	 * @return bool
6404
+	 */
6405
+	public function is_logic_query_param_key($query_param_key)
6406
+	{
6407
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6408
+			if (
6409
+				$query_param_key === $logic_query_param_key
6410
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6411
+			) {
6412
+				return true;
6413
+			}
6414
+		}
6415
+		return false;
6416
+	}
6417
+
6418
+
6419
+	/**
6420
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6421
+	 *
6422
+	 * @return boolean
6423
+	 * @since 4.9.74.p
6424
+	 */
6425
+	public function hasPassword()
6426
+	{
6427
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6428
+		if ($this->has_password_field === null) {
6429
+			$password_field           = $this->getPasswordField();
6430
+			$this->has_password_field = $password_field instanceof EE_Password_Field;
6431
+		}
6432
+		return $this->has_password_field;
6433
+	}
6434
+
6435
+
6436
+	/**
6437
+	 * Returns the password field on this model, if there is one
6438
+	 *
6439
+	 * @return EE_Password_Field|null
6440
+	 * @since 4.9.74.p
6441
+	 */
6442
+	public function getPasswordField()
6443
+	{
6444
+		// if we definitely already know there is a password field or not (because has_password_field is true or false)
6445
+		// there's no need to search for it. If we don't know yet, then find out
6446
+		if ($this->has_password_field === null && $this->password_field === null) {
6447
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6448
+		}
6449
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6450
+		return $this->password_field;
6451
+	}
6452
+
6453
+
6454
+	/**
6455
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6456
+	 *
6457
+	 * @return EE_Model_Field_Base[]
6458
+	 * @throws EE_Error
6459
+	 * @since 4.9.74.p
6460
+	 */
6461
+	public function getPasswordProtectedFields()
6462
+	{
6463
+		$password_field = $this->getPasswordField();
6464
+		$fields         = [];
6465
+		if ($password_field instanceof EE_Password_Field) {
6466
+			$field_names = $password_field->protectedFields();
6467
+			foreach ($field_names as $field_name) {
6468
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6469
+			}
6470
+		}
6471
+		return $fields;
6472
+	}
6473
+
6474
+
6475
+	/**
6476
+	 * Checks if the current user can perform the requested action on this model
6477
+	 *
6478
+	 * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6479
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6480
+	 * @return bool
6481
+	 * @throws EE_Error
6482
+	 * @throws InvalidArgumentException
6483
+	 * @throws InvalidDataTypeException
6484
+	 * @throws InvalidInterfaceException
6485
+	 * @throws ReflectionException
6486
+	 * @throws UnexpectedEntityException
6487
+	 * @since 4.9.74.p
6488
+	 */
6489
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6490
+	{
6491
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6492
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6493
+		}
6494
+		if (! is_array($model_obj_or_fields_n_values)) {
6495
+			throw new UnexpectedEntityException(
6496
+				$model_obj_or_fields_n_values,
6497
+				'EE_Base_Class',
6498
+				sprintf(
6499
+					esc_html__(
6500
+						'%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6501
+						'event_espresso'
6502
+					),
6503
+					__FUNCTION__
6504
+				)
6505
+			);
6506
+		}
6507
+		return $this->exists(
6508
+			$this->alter_query_params_to_restrict_by_ID(
6509
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6510
+				[
6511
+					'default_where_conditions' => 'none',
6512
+					'caps'                     => $cap_to_check,
6513
+				]
6514
+			)
6515
+		);
6516
+	}
6517
+
6518
+
6519
+	/**
6520
+	 * Returns the query param where conditions key to the password affecting this model.
6521
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6522
+	 *
6523
+	 * @return null|string
6524
+	 * @throws EE_Error
6525
+	 * @throws InvalidArgumentException
6526
+	 * @throws InvalidDataTypeException
6527
+	 * @throws InvalidInterfaceException
6528
+	 * @throws ModelConfigurationException
6529
+	 * @throws ReflectionException
6530
+	 * @since 4.9.74.p
6531
+	 */
6532
+	public function modelChainAndPassword()
6533
+	{
6534
+		if ($this->model_chain_to_password === null) {
6535
+			throw new ModelConfigurationException(
6536
+				$this,
6537
+				esc_html_x(
6538
+				// @codingStandardsIgnoreStart
6539
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6540
+					// @codingStandardsIgnoreEnd
6541
+					'1: model name',
6542
+					'event_espresso'
6543
+				)
6544
+			);
6545
+		}
6546
+		if ($this->model_chain_to_password === '') {
6547
+			$model_with_password = $this;
6548
+		} else {
6549
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6550
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6551
+			} else {
6552
+				$last_model_in_chain = $this->model_chain_to_password;
6553
+			}
6554
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6555
+		}
6556
+
6557
+		$password_field = $model_with_password->getPasswordField();
6558
+		if ($password_field instanceof EE_Password_Field) {
6559
+			$password_field_name = $password_field->get_name();
6560
+		} else {
6561
+			throw new ModelConfigurationException(
6562
+				$this,
6563
+				sprintf(
6564
+					esc_html_x(
6565
+						'This model claims related model "%1$s" should have a password field on it, but none was found. The model relation chain is "%2$s"',
6566
+						'1: model name, 2: special string',
6567
+						'event_espresso'
6568
+					),
6569
+					$model_with_password->get_this_model_name(),
6570
+					$this->model_chain_to_password
6571
+				)
6572
+			);
6573
+		}
6574
+		return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6575
+	}
6576
+
6577
+
6578
+	/**
6579
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6580
+	 * or if this model itself has a password affecting access to some of its other fields.
6581
+	 *
6582
+	 * @return boolean
6583
+	 * @since 4.9.74.p
6584
+	 */
6585
+	public function restrictedByRelatedModelPassword()
6586
+	{
6587
+		return $this->model_chain_to_password !== null;
6588
+	}
6589 6589
 }
Please login to merge, or discard this patch.
Spacing   +245 added lines, -245 removed lines patch added patch discarded remove patch
@@ -567,7 +567,7 @@  discard block
 block discarded – undo
567 567
     protected function __construct($timezone = null)
568 568
     {
569 569
         // check that the model has not been loaded too soon
570
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
570
+        if ( ! did_action('AHEE__EE_System__load_espresso_addons')) {
571 571
             throw new EE_Error(
572 572
                 sprintf(
573 573
                     esc_html__(
@@ -590,7 +590,7 @@  discard block
 block discarded – undo
590 590
          *
591 591
          * @var EE_Table_Base[] $_tables
592 592
          */
593
-        $this->_tables = (array)apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
593
+        $this->_tables = (array) apply_filters('FHEE__'.get_class($this).'__construct__tables', $this->_tables);
594 594
         foreach ($this->_tables as $table_alias => $table_obj) {
595 595
             /** @var $table_obj EE_Table_Base */
596 596
             $table_obj->_construct_finalize_with_alias($table_alias);
@@ -605,10 +605,10 @@  discard block
 block discarded – undo
605 605
          *
606 606
          * @param EE_Model_Field_Base[] $_fields
607 607
          */
608
-        $this->_fields = (array)apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
608
+        $this->_fields = (array) apply_filters('FHEE__'.get_class($this).'__construct__fields', $this->_fields);
609 609
         $this->_invalidate_field_caches();
610 610
         foreach ($this->_fields as $table_alias => $fields_for_table) {
611
-            if (! array_key_exists($table_alias, $this->_tables)) {
611
+            if ( ! array_key_exists($table_alias, $this->_tables)) {
612 612
                 throw new EE_Error(
613 613
                     sprintf(
614 614
                         esc_html__(
@@ -644,8 +644,8 @@  discard block
 block discarded – undo
644 644
          *
645 645
          * @param EE_Model_Relation_Base[] $_model_relations
646 646
          */
647
-        $this->_model_relations = (array)apply_filters(
648
-            'FHEE__' . get_class($this) . '__construct__model_relations',
647
+        $this->_model_relations = (array) apply_filters(
648
+            'FHEE__'.get_class($this).'__construct__model_relations',
649 649
             $this->_model_relations
650 650
         );
651 651
         foreach ($this->_model_relations as $model_name => $relation_obj) {
@@ -657,12 +657,12 @@  discard block
 block discarded – undo
657 657
         }
658 658
         $this->set_timezone($timezone);
659 659
         // finalize default where condition strategy, or set default
660
-        if (! $this->_default_where_conditions_strategy) {
660
+        if ( ! $this->_default_where_conditions_strategy) {
661 661
             // nothing was set during child constructor, so set default
662 662
             $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
663 663
         }
664 664
         $this->_default_where_conditions_strategy->_finalize_construct($this);
665
-        if (! $this->_minimum_where_conditions_strategy) {
665
+        if ( ! $this->_minimum_where_conditions_strategy) {
666 666
             // nothing was set during child constructor, so set default
667 667
             $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
668 668
         }
@@ -675,8 +675,8 @@  discard block
 block discarded – undo
675 675
         // initialize the standard cap restriction generators if none were specified by the child constructor
676 676
         if ($this->_cap_restriction_generators !== false) {
677 677
             foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
678
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
679
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
678
+                if ( ! isset($this->_cap_restriction_generators[$cap_context])) {
679
+                    $this->_cap_restriction_generators[$cap_context] = apply_filters(
680 680
                         'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
681 681
                         new EE_Restriction_Generator_Protected(),
682 682
                         $cap_context,
@@ -688,10 +688,10 @@  discard block
 block discarded – undo
688 688
         // if there are cap restriction generators, use them to make the default cap restrictions
689 689
         if ($this->_cap_restriction_generators !== false) {
690 690
             foreach ($this->_cap_restriction_generators as $context => $generator_object) {
691
-                if (! $generator_object) {
691
+                if ( ! $generator_object) {
692 692
                     continue;
693 693
                 }
694
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
694
+                if ( ! $generator_object instanceof EE_Restriction_Generator_Base) {
695 695
                     throw new EE_Error(
696 696
                         sprintf(
697 697
                             esc_html__(
@@ -704,12 +704,12 @@  discard block
 block discarded – undo
704 704
                     );
705 705
                 }
706 706
                 $action = $this->cap_action_for_context($context);
707
-                if (! $generator_object->construction_finalized()) {
707
+                if ( ! $generator_object->construction_finalized()) {
708 708
                     $generator_object->_construct_finalize($this, $action);
709 709
                 }
710 710
             }
711 711
         }
712
-        do_action('AHEE__' . get_class($this) . '__construct__end');
712
+        do_action('AHEE__'.get_class($this).'__construct__end');
713 713
     }
714 714
 
715 715
 
@@ -721,7 +721,7 @@  discard block
 block discarded – undo
721 721
      */
722 722
     public static function set_model_query_blog_id($blog_id = 0)
723 723
     {
724
-        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int)$blog_id : get_current_blog_id();
724
+        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int) $blog_id : get_current_blog_id();
725 725
     }
726 726
 
727 727
 
@@ -753,7 +753,7 @@  discard block
 block discarded – undo
753 753
     public static function instance($timezone = null)
754 754
     {
755 755
         // check if instance of Espresso_model already exists
756
-        if (! static::$_instance instanceof static) {
756
+        if ( ! static::$_instance instanceof static) {
757 757
             // instantiate Espresso_model
758 758
             static::$_instance = new static(
759 759
                 $timezone,
@@ -791,7 +791,7 @@  discard block
 block discarded – undo
791 791
             foreach ($r->getDefaultProperties() as $property => $value) {
792 792
                 // don't set instance to null like it was originally,
793 793
                 // but it's static anyways, and we're ignoring static properties (for now at least)
794
-                if (! isset($static_properties[ $property ])) {
794
+                if ( ! isset($static_properties[$property])) {
795 795
                     static::$_instance->{$property} = $value;
796 796
                 }
797 797
             }
@@ -814,7 +814,7 @@  discard block
 block discarded – undo
814 814
      */
815 815
     private static function getLoader()
816 816
     {
817
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
817
+        if ( ! EEM_Base::$loader instanceof LoaderInterface) {
818 818
             EEM_Base::$loader = LoaderFactory::getLoader();
819 819
         }
820 820
         return EEM_Base::$loader;
@@ -834,7 +834,7 @@  discard block
 block discarded – undo
834 834
      */
835 835
     public function status_array($translated = false)
836 836
     {
837
-        if (! array_key_exists('Status', $this->_model_relations)) {
837
+        if ( ! array_key_exists('Status', $this->_model_relations)) {
838 838
             return [];
839 839
         }
840 840
         $model_name   = $this->get_this_model_name();
@@ -842,7 +842,7 @@  discard block
 block discarded – undo
842 842
         $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
843 843
         $status_array = [];
844 844
         foreach ($stati as $status) {
845
-            $status_array[ $status->ID() ] = $status->get('STS_code');
845
+            $status_array[$status->ID()] = $status->get('STS_code');
846 846
         }
847 847
         return $translated
848 848
             ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
@@ -905,7 +905,7 @@  discard block
 block discarded – undo
905 905
     {
906 906
         $wp_user_field_name = $this->wp_user_field_name();
907 907
         if ($wp_user_field_name) {
908
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
908
+            $query_params[0][$wp_user_field_name] = get_current_user_id();
909 909
         }
910 910
         return $query_params;
911 911
     }
@@ -923,17 +923,17 @@  discard block
 block discarded – undo
923 923
     public function wp_user_field_name()
924 924
     {
925 925
         try {
926
-            if (! empty($this->_model_chain_to_wp_user)) {
926
+            if ( ! empty($this->_model_chain_to_wp_user)) {
927 927
                 $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
928 928
                 $last_model_name              = end($models_to_follow_to_wp_users);
929 929
                 $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
930
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
930
+                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user.'.';
931 931
             } else {
932 932
                 $model_with_fk_to_wp_users = $this;
933 933
                 $model_chain_to_wp_user    = '';
934 934
             }
935 935
             $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
936
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
936
+            return $model_chain_to_wp_user.$wp_user_field->get_name();
937 937
         } catch (EE_Error $e) {
938 938
             return false;
939 939
         }
@@ -1009,11 +1009,11 @@  discard block
 block discarded – undo
1009 1009
         if ($this->_custom_selections instanceof CustomSelects) {
1010 1010
             $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1011 1011
             $select_expressions .= $select_expressions
1012
-                ? ', ' . $custom_expressions
1012
+                ? ', '.$custom_expressions
1013 1013
                 : $custom_expressions;
1014 1014
         }
1015 1015
 
1016
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1016
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1017 1017
         return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1018 1018
     }
1019 1019
 
@@ -1030,7 +1030,7 @@  discard block
 block discarded – undo
1030 1030
      */
1031 1031
     protected function getCustomSelection(array $query_params, $columns_to_select = null)
1032 1032
     {
1033
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1033
+        if ( ! isset($query_params['extra_selects']) && $columns_to_select === null) {
1034 1034
             return null;
1035 1035
         }
1036 1036
         $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
@@ -1079,7 +1079,7 @@  discard block
 block discarded – undo
1079 1079
         if (is_array($columns_to_select)) {
1080 1080
             $select_sql_array = [];
1081 1081
             foreach ($columns_to_select as $alias => $selection_and_datatype) {
1082
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1082
+                if ( ! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1083 1083
                     throw new EE_Error(
1084 1084
                         sprintf(
1085 1085
                             esc_html__(
@@ -1091,7 +1091,7 @@  discard block
 block discarded – undo
1091 1091
                         )
1092 1092
                     );
1093 1093
                 }
1094
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1094
+                if ( ! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1095 1095
                     throw new EE_Error(
1096 1096
                         sprintf(
1097 1097
                             esc_html__(
@@ -1163,12 +1163,12 @@  discard block
 block discarded – undo
1163 1163
      */
1164 1164
     public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1165 1165
     {
1166
-        if (! isset($query_params[0])) {
1166
+        if ( ! isset($query_params[0])) {
1167 1167
             $query_params[0] = [];
1168 1168
         }
1169 1169
         $conditions_from_id = $this->parse_index_primary_key_string($id);
1170 1170
         if ($conditions_from_id === null) {
1171
-            $query_params[0][ $this->primary_key_name() ] = $id;
1171
+            $query_params[0][$this->primary_key_name()] = $id;
1172 1172
         } else {
1173 1173
             // no primary key, so the $id must be from the get_index_primary_key_string()
1174 1174
             $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
@@ -1188,7 +1188,7 @@  discard block
 block discarded – undo
1188 1188
      */
1189 1189
     public function get_one($query_params = [])
1190 1190
     {
1191
-        if (! is_array($query_params)) {
1191
+        if ( ! is_array($query_params)) {
1192 1192
             EE_Error::doing_it_wrong(
1193 1193
                 'EEM_Base::get_one',
1194 1194
                 sprintf(
@@ -1388,7 +1388,7 @@  discard block
 block discarded – undo
1388 1388
                 return [];
1389 1389
             }
1390 1390
         }
1391
-        if (! is_array($query_params)) {
1391
+        if ( ! is_array($query_params)) {
1392 1392
             EE_Error::doing_it_wrong(
1393 1393
                 'EEM_Base::_get_consecutive',
1394 1394
                 sprintf(
@@ -1400,10 +1400,10 @@  discard block
 block discarded – undo
1400 1400
             $query_params = [];
1401 1401
         }
1402 1402
         // let's add the where query param for consecutive look up.
1403
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1403
+        $query_params[0][$field_to_order_by] = [$operand, $current_field_value];
1404 1404
         $query_params['limit']                 = $limit;
1405 1405
         // set direction
1406
-        $incoming_orderby         = isset($query_params['order_by']) ? (array)$query_params['order_by'] : [];
1406
+        $incoming_orderby         = isset($query_params['order_by']) ? (array) $query_params['order_by'] : [];
1407 1407
         $query_params['order_by'] = $operand === '>'
1408 1408
             ? [$field_to_order_by => 'ASC'] + $incoming_orderby
1409 1409
             : [$field_to_order_by => 'DESC'] + $incoming_orderby;
@@ -1424,7 +1424,7 @@  discard block
 block discarded – undo
1424 1424
     public function set_timezone($timezone)
1425 1425
     {
1426 1426
         // don't set the timezone if the incoming value is the same
1427
-        if (! empty($timezone) && $timezone === $this->_timezone) {
1427
+        if ( ! empty($timezone) && $timezone === $this->_timezone) {
1428 1428
             return;
1429 1429
         }
1430 1430
         $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
@@ -1480,7 +1480,7 @@  discard block
 block discarded – undo
1480 1480
     {
1481 1481
         $field_settings = $this->field_settings_for($field_name);
1482 1482
         // if not a valid EE_Datetime_Field then throw error
1483
-        if (! $field_settings instanceof EE_Datetime_Field) {
1483
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1484 1484
             throw new EE_Error(
1485 1485
                 sprintf(
1486 1486
                     esc_html__(
@@ -1631,7 +1631,7 @@  discard block
 block discarded – undo
1631 1631
      */
1632 1632
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1633 1633
     {
1634
-        if (! is_array($query_params)) {
1634
+        if ( ! is_array($query_params)) {
1635 1635
             EE_Error::doing_it_wrong(
1636 1636
                 'EEM_Base::update',
1637 1637
                 sprintf(
@@ -1660,7 +1660,7 @@  discard block
 block discarded – undo
1660 1660
          * @param array    $query_params
1661 1661
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1662 1662
          */
1663
-        $fields_n_values = (array)apply_filters(
1663
+        $fields_n_values = (array) apply_filters(
1664 1664
             'FHEE__EEM_Base__update__fields_n_values',
1665 1665
             $fields_n_values,
1666 1666
             $this,
@@ -1678,10 +1678,10 @@  discard block
 block discarded – undo
1678 1678
         $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1679 1679
         foreach ($wpdb_select_results as $wpdb_result) {
1680 1680
             // type cast stdClass as array
1681
-            $wpdb_result = (array)$wpdb_result;
1681
+            $wpdb_result = (array) $wpdb_result;
1682 1682
             // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1683 1683
             if ($this->has_primary_key_field()) {
1684
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1684
+                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1685 1685
             } else {
1686 1686
                 // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1687 1687
                 $main_table_pk_value = null;
@@ -1697,7 +1697,7 @@  discard block
 block discarded – undo
1697 1697
                     // in this table, right? so insert a row in the current table, using any fields available
1698 1698
                     if (
1699 1699
                     ! (array_key_exists($this_table_pk_column, $wpdb_result)
1700
-                       && $wpdb_result[ $this_table_pk_column ])
1700
+                       && $wpdb_result[$this_table_pk_column])
1701 1701
                     ) {
1702 1702
                         $success = $this->_insert_into_specific_table(
1703 1703
                             $table_obj,
@@ -1705,7 +1705,7 @@  discard block
 block discarded – undo
1705 1705
                             $main_table_pk_value
1706 1706
                         );
1707 1707
                         // if we died here, report the error
1708
-                        if (! $success) {
1708
+                        if ( ! $success) {
1709 1709
                             return false;
1710 1710
                         }
1711 1711
                     }
@@ -1733,10 +1733,10 @@  discard block
 block discarded – undo
1733 1733
                 $model_objs_affected_ids     = [];
1734 1734
                 foreach ($models_affected_key_columns as $row) {
1735 1735
                     $combined_index_key                             = $this->get_index_primary_key_string($row);
1736
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1736
+                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1737 1737
                 }
1738 1738
             }
1739
-            if (! $model_objs_affected_ids) {
1739
+            if ( ! $model_objs_affected_ids) {
1740 1740
                 // wait wait wait- if nothing was affected let's stop here
1741 1741
                 return 0;
1742 1742
             }
@@ -1764,8 +1764,8 @@  discard block
 block discarded – undo
1764 1764
                             . " SET "
1765 1765
                             . $this->_construct_update_sql($fields_n_values)
1766 1766
                             . $model_query_info->get_where_sql(
1767
-            );// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1768
-        $rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1767
+            ); // note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1768
+        $rows_affected = $this->_do_wpdb_query('query', [$SQL]);
1769 1769
         /**
1770 1770
          * Action called after a model update call has been made.
1771 1771
          *
@@ -1776,7 +1776,7 @@  discard block
 block discarded – undo
1776 1776
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1777 1777
          */
1778 1778
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1779
-        return $rows_affected;// how many supposedly got updated
1779
+        return $rows_affected; // how many supposedly got updated
1780 1780
     }
1781 1781
 
1782 1782
 
@@ -1807,7 +1807,7 @@  discard block
 block discarded – undo
1807 1807
         $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1808 1808
         $select_expressions = $field->get_qualified_column();
1809 1809
         $SQL                =
1810
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1810
+            "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1811 1811
         return $this->_do_wpdb_query('get_col', [$SQL]);
1812 1812
     }
1813 1813
 
@@ -1827,7 +1827,7 @@  discard block
 block discarded – undo
1827 1827
     {
1828 1828
         $query_params['limit'] = 1;
1829 1829
         $col                   = $this->get_col($query_params, $field_to_select);
1830
-        if (! empty($col)) {
1830
+        if ( ! empty($col)) {
1831 1831
             return reset($col);
1832 1832
         }
1833 1833
         return null;
@@ -1856,7 +1856,7 @@  discard block
 block discarded – undo
1856 1856
             $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1857 1857
             $value_sql       = $prepared_value === null ? 'NULL'
1858 1858
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1859
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1859
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1860 1860
         }
1861 1861
         return implode(",", $cols_n_values);
1862 1862
     }
@@ -1992,7 +1992,7 @@  discard block
 block discarded – undo
1992 1992
                                 . $model_query_info->get_full_join_sql()
1993 1993
                                 . " WHERE "
1994 1994
                                 . $deletion_where_query_part;
1995
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
1995
+            $rows_deleted = $this->_do_wpdb_query('query', [$SQL]);
1996 1996
         } else {
1997 1997
             $rows_deleted = 0;
1998 1998
         }
@@ -2002,12 +2002,12 @@  discard block
 block discarded – undo
2002 2002
         if (
2003 2003
             $this->has_primary_key_field()
2004 2004
             && $rows_deleted !== false
2005
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2005
+            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2006 2006
         ) {
2007
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2007
+            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2008 2008
             foreach ($ids_for_removal as $id) {
2009
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2010
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2009
+                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2010
+                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2011 2011
                 }
2012 2012
             }
2013 2013
 
@@ -2047,7 +2047,7 @@  discard block
 block discarded – undo
2047 2047
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2048 2048
          */
2049 2049
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2050
-        return $rows_deleted;// how many supposedly got deleted
2050
+        return $rows_deleted; // how many supposedly got deleted
2051 2051
     }
2052 2052
 
2053 2053
 
@@ -2143,15 +2143,15 @@  discard block
 block discarded – undo
2143 2143
                 if (
2144 2144
                     $allow_blocking
2145 2145
                     && $this->delete_is_blocked_by_related_models(
2146
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2146
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2147 2147
                     )
2148 2148
                 ) {
2149 2149
                     continue;
2150 2150
                 }
2151 2151
                 // primary table deletes
2152
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2153
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2154
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2152
+                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2153
+                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2154
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2155 2155
                 }
2156 2156
             }
2157 2157
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2160,8 +2160,8 @@  discard block
 block discarded – undo
2160 2160
                 $ids_to_delete_indexed_by_column_for_row = [];
2161 2161
                 foreach ($fields as $cpk_field) {
2162 2162
                     if ($cpk_field instanceof EE_Model_Field_Base) {
2163
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2164
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2163
+                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2164
+                            $item_to_delete[$cpk_field->get_qualified_column()];
2165 2165
                     }
2166 2166
                 }
2167 2167
                 $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
@@ -2201,7 +2201,7 @@  discard block
 block discarded – undo
2201 2201
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2202 2202
                 // make sure we have unique $ids
2203 2203
                 $ids     = array_unique($ids);
2204
-                $query[] = $column . ' IN(' . implode(',', $ids) . ')';
2204
+                $query[] = $column.' IN('.implode(',', $ids).')';
2205 2205
             }
2206 2206
             $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2207 2207
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2209,7 +2209,7 @@  discard block
 block discarded – undo
2209 2209
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2210 2210
                 $values_for_each_combined_primary_key_for_a_row = [];
2211 2211
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2212
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2212
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2213 2213
                 }
2214 2214
                 $ways_to_identify_a_row[] = '('
2215 2215
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2283,10 +2283,10 @@  discard block
 block discarded – undo
2283 2283
                 $column_to_count = '*';
2284 2284
             }
2285 2285
         }
2286
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2286
+        $column_to_count = $distinct ? "DISTINCT ".$column_to_count : $column_to_count;
2287 2287
         $SQL             =
2288
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2289
-        return (int)$this->_do_wpdb_query('get_var', [$SQL]);
2288
+            "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2289
+        return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2290 2290
     }
2291 2291
 
2292 2292
 
@@ -2310,14 +2310,14 @@  discard block
 block discarded – undo
2310 2310
         }
2311 2311
         $column_to_count = $field_obj->get_qualified_column();
2312 2312
         $SQL             =
2313
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2313
+            "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2314 2314
         $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2315 2315
         $data_type       = $field_obj->get_wpdb_data_type();
2316 2316
         if ($data_type === '%d' || $data_type === '%s') {
2317
-            return (float)$return_value;
2317
+            return (float) $return_value;
2318 2318
         }
2319 2319
         // must be %f
2320
-        return (float)$return_value;
2320
+        return (float) $return_value;
2321 2321
     }
2322 2322
 
2323 2323
 
@@ -2336,7 +2336,7 @@  discard block
 block discarded – undo
2336 2336
         // if we're in maintenance mode level 2, DON'T run any queries
2337 2337
         // because level 2 indicates the database needs updating and
2338 2338
         // is probably out of sync with the code
2339
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2339
+        if ( ! EE_Maintenance_Mode::instance()->models_can_query()) {
2340 2340
             throw new EE_Error(
2341 2341
                 sprintf(
2342 2342
                     esc_html__(
@@ -2347,7 +2347,7 @@  discard block
 block discarded – undo
2347 2347
             );
2348 2348
         }
2349 2349
         global $wpdb;
2350
-        if (! method_exists($wpdb, $wpdb_method)) {
2350
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2351 2351
             throw new EE_Error(
2352 2352
                 sprintf(
2353 2353
                     esc_html__(
@@ -2366,7 +2366,7 @@  discard block
 block discarded – undo
2366 2366
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2367 2367
         if (WP_DEBUG) {
2368 2368
             $wpdb->show_errors($old_show_errors_value);
2369
-            if (! empty($wpdb->last_error)) {
2369
+            if ( ! empty($wpdb->last_error)) {
2370 2370
                 throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2371 2371
             }
2372 2372
             if ($result === false) {
@@ -2436,7 +2436,7 @@  discard block
 block discarded – undo
2436 2436
                     // ummmm... you in trouble
2437 2437
                     return $result;
2438 2438
             }
2439
-            if (! empty($error_message)) {
2439
+            if ( ! empty($error_message)) {
2440 2440
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2441 2441
                 trigger_error($error_message);
2442 2442
             }
@@ -2513,11 +2513,11 @@  discard block
 block discarded – undo
2513 2513
      */
2514 2514
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2515 2515
     {
2516
-        return " FROM " . $model_query_info->get_full_join_sql() .
2517
-               $model_query_info->get_where_sql() .
2518
-               $model_query_info->get_group_by_sql() .
2519
-               $model_query_info->get_having_sql() .
2520
-               $model_query_info->get_order_by_sql() .
2516
+        return " FROM ".$model_query_info->get_full_join_sql().
2517
+               $model_query_info->get_where_sql().
2518
+               $model_query_info->get_group_by_sql().
2519
+               $model_query_info->get_having_sql().
2520
+               $model_query_info->get_order_by_sql().
2521 2521
                $model_query_info->get_limit_sql();
2522 2522
     }
2523 2523
 
@@ -2710,12 +2710,12 @@  discard block
 block discarded – undo
2710 2710
         $related_model = $this->get_related_model_obj($model_name);
2711 2711
         // we're just going to use the query params on the related model's normal get_all query,
2712 2712
         // except add a condition to say to match the current mod
2713
-        if (! isset($query_params['default_where_conditions'])) {
2713
+        if ( ! isset($query_params['default_where_conditions'])) {
2714 2714
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2715 2715
         }
2716 2716
         $this_model_name                                                 = $this->get_this_model_name();
2717 2717
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2718
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2718
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2719 2719
         return $related_model->count($query_params, $field_to_count, $distinct);
2720 2720
     }
2721 2721
 
@@ -2736,7 +2736,7 @@  discard block
 block discarded – undo
2736 2736
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2737 2737
     {
2738 2738
         $related_model = $this->get_related_model_obj($model_name);
2739
-        if (! is_array($query_params)) {
2739
+        if ( ! is_array($query_params)) {
2740 2740
             EE_Error::doing_it_wrong(
2741 2741
                 'EEM_Base::sum_related',
2742 2742
                 sprintf(
@@ -2749,12 +2749,12 @@  discard block
 block discarded – undo
2749 2749
         }
2750 2750
         // we're just going to use the query params on the related model's normal get_all query,
2751 2751
         // except add a condition to say to match the current mod
2752
-        if (! isset($query_params['default_where_conditions'])) {
2752
+        if ( ! isset($query_params['default_where_conditions'])) {
2753 2753
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2754 2754
         }
2755 2755
         $this_model_name                                                 = $this->get_this_model_name();
2756 2756
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2757
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2757
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2758 2758
         return $related_model->sum($query_params, $field_to_sum);
2759 2759
     }
2760 2760
 
@@ -2806,7 +2806,7 @@  discard block
 block discarded – undo
2806 2806
                 $field_with_model_name = $field;
2807 2807
             }
2808 2808
         }
2809
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2809
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2810 2810
             throw new EE_Error(
2811 2811
                 sprintf(
2812 2812
                     esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
@@ -2843,7 +2843,7 @@  discard block
 block discarded – undo
2843 2843
          * @param array    $fields_n_values keys are the fields and values are their new values
2844 2844
          * @param EEM_Base $model           the model used
2845 2845
          */
2846
-        $field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2846
+        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2847 2847
         if ($this->_satisfies_unique_indexes($field_n_values)) {
2848 2848
             $main_table = $this->_get_main_table();
2849 2849
             $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
@@ -2945,14 +2945,14 @@  discard block
 block discarded – undo
2945 2945
                 || $this->get_primary_key_field()
2946 2946
                    instanceof
2947 2947
                    EE_Primary_Key_String_Field)
2948
-            && isset($fields_n_values[ $this->primary_key_name() ])
2948
+            && isset($fields_n_values[$this->primary_key_name()])
2949 2949
         ) {
2950
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2950
+            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
2951 2951
         }
2952 2952
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2953 2953
             $uniqueness_where_params                              =
2954 2954
                 array_intersect_key($fields_n_values, $unique_index->fields());
2955
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2955
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
2956 2956
         }
2957 2957
         // if there is nothing to base this search on, then we shouldn't find anything
2958 2958
         if (empty($query_params)) {
@@ -3029,16 +3029,16 @@  discard block
 block discarded – undo
3029 3029
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3030 3030
             // if the value we want to assign it to is NULL, just don't mention it for the insertion
3031 3031
             if ($prepared_value !== null) {
3032
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3032
+                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3033 3033
                 $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3034 3034
             }
3035 3035
         }
3036 3036
         if ($table instanceof EE_Secondary_Table && $new_id) {
3037 3037
             // its not the main table, so we should have already saved the main table's PK which we just inserted
3038 3038
             // so add the fk to the main table as a column
3039
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3039
+            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3040 3040
             $format_for_insertion[]                              =
3041
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3041
+                '%d'; // yes right now we're only allowing these foreign keys to be INTs
3042 3042
         }
3043 3043
         // insert the new entry
3044 3044
         $result = $this->_do_wpdb_query(
@@ -3055,7 +3055,7 @@  discard block
 block discarded – undo
3055 3055
             }
3056 3056
             // it's not an auto-increment primary key, so
3057 3057
             // it must have been supplied
3058
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3058
+            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3059 3059
         }
3060 3060
         // we can't return a  primary key because there is none. instead return
3061 3061
         // a unique string indicating this model
@@ -3079,14 +3079,14 @@  discard block
 block discarded – undo
3079 3079
         if (
3080 3080
             ! $field_obj->is_nullable()
3081 3081
             && (
3082
-                ! isset($fields_n_values[ $field_obj->get_name() ])
3083
-                || $fields_n_values[ $field_obj->get_name() ] === null
3082
+                ! isset($fields_n_values[$field_obj->get_name()])
3083
+                || $fields_n_values[$field_obj->get_name()] === null
3084 3084
             )
3085 3085
         ) {
3086
-            $fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3086
+            $fields_n_values[$field_obj->get_name()] = $field_obj->get_default_value();
3087 3087
         }
3088
-        $unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3089
-            ? $fields_n_values[ $field_obj->get_name() ]
3088
+        $unprepared_value = isset($fields_n_values[$field_obj->get_name()])
3089
+            ? $fields_n_values[$field_obj->get_name()]
3090 3090
             : null;
3091 3091
         return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3092 3092
     }
@@ -3187,7 +3187,7 @@  discard block
 block discarded – undo
3187 3187
      */
3188 3188
     public function get_table_obj_by_alias($table_alias = '')
3189 3189
     {
3190
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3190
+        return isset($this->_tables[$table_alias]) ? $this->_tables[$table_alias] : null;
3191 3191
     }
3192 3192
 
3193 3193
 
@@ -3201,7 +3201,7 @@  discard block
 block discarded – undo
3201 3201
         $other_tables = [];
3202 3202
         foreach ($this->_tables as $table_alias => $table) {
3203 3203
             if ($table instanceof EE_Secondary_Table) {
3204
-                $other_tables[ $table_alias ] = $table;
3204
+                $other_tables[$table_alias] = $table;
3205 3205
             }
3206 3206
         }
3207 3207
         return $other_tables;
@@ -3216,7 +3216,7 @@  discard block
 block discarded – undo
3216 3216
      */
3217 3217
     public function _get_fields_for_table($table_alias)
3218 3218
     {
3219
-        return $this->_fields[ $table_alias ];
3219
+        return $this->_fields[$table_alias];
3220 3220
     }
3221 3221
 
3222 3222
 
@@ -3245,7 +3245,7 @@  discard block
 block discarded – undo
3245 3245
                     $query_info_carrier,
3246 3246
                     'group_by'
3247 3247
                 );
3248
-            } elseif (! empty($query_params['group_by'])) {
3248
+            } elseif ( ! empty($query_params['group_by'])) {
3249 3249
                 $this->_extract_related_model_info_from_query_param(
3250 3250
                     $query_params['group_by'],
3251 3251
                     $query_info_carrier,
@@ -3267,7 +3267,7 @@  discard block
 block discarded – undo
3267 3267
                     $query_info_carrier,
3268 3268
                     'order_by'
3269 3269
                 );
3270
-            } elseif (! empty($query_params['order_by'])) {
3270
+            } elseif ( ! empty($query_params['order_by'])) {
3271 3271
                 $this->_extract_related_model_info_from_query_param(
3272 3272
                     $query_params['order_by'],
3273 3273
                     $query_info_carrier,
@@ -3302,8 +3302,8 @@  discard block
 block discarded – undo
3302 3302
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3303 3303
         $query_param_type
3304 3304
     ) {
3305
-        if (! empty($sub_query_params)) {
3306
-            $sub_query_params = (array)$sub_query_params;
3305
+        if ( ! empty($sub_query_params)) {
3306
+            $sub_query_params = (array) $sub_query_params;
3307 3307
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3308 3308
                 // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3309 3309
                 $this->_extract_related_model_info_from_query_param(
@@ -3318,7 +3318,7 @@  discard block
 block discarded – undo
3318 3318
                 $query_param_sans_stars =
3319 3319
                     $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3320 3320
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3321
-                    if (! is_array($possibly_array_of_params)) {
3321
+                    if ( ! is_array($possibly_array_of_params)) {
3322 3322
                         throw new EE_Error(
3323 3323
                             sprintf(
3324 3324
                                 esc_html__(
@@ -3344,7 +3344,7 @@  discard block
 block discarded – undo
3344 3344
                     // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3345 3345
                     // indicating that $possible_array_of_params[1] is actually a field name,
3346 3346
                     // from which we should extract query parameters!
3347
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3347
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3348 3348
                         throw new EE_Error(
3349 3349
                             sprintf(
3350 3350
                                 esc_html__(
@@ -3384,8 +3384,8 @@  discard block
 block discarded – undo
3384 3384
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3385 3385
         $query_param_type
3386 3386
     ) {
3387
-        if (! empty($sub_query_params)) {
3388
-            if (! is_array($sub_query_params)) {
3387
+        if ( ! empty($sub_query_params)) {
3388
+            if ( ! is_array($sub_query_params)) {
3389 3389
                 throw new EE_Error(
3390 3390
                     sprintf(
3391 3391
                         esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
@@ -3423,7 +3423,7 @@  discard block
 block discarded – undo
3423 3423
      */
3424 3424
     public function _create_model_query_info_carrier($query_params)
3425 3425
     {
3426
-        if (! is_array($query_params)) {
3426
+        if ( ! is_array($query_params)) {
3427 3427
             EE_Error::doing_it_wrong(
3428 3428
                 'EEM_Base::_create_model_query_info_carrier',
3429 3429
                 sprintf(
@@ -3456,7 +3456,7 @@  discard block
 block discarded – undo
3456 3456
             // only include if related to a cpt where no password has been set
3457 3457
             $query_params[0]['OR*nopassword'] = [
3458 3458
                 $where_param_key_for_password       => '',
3459
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3459
+                $where_param_key_for_password.'*' => ['IS_NULL'],
3460 3460
             ];
3461 3461
         }
3462 3462
         $query_object = $this->_extract_related_models_from_query($query_params);
@@ -3510,7 +3510,7 @@  discard block
 block discarded – undo
3510 3510
         // set limit
3511 3511
         if (array_key_exists('limit', $query_params)) {
3512 3512
             if (is_array($query_params['limit'])) {
3513
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3513
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3514 3514
                     $e = sprintf(
3515 3515
                         esc_html__(
3516 3516
                             "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
@@ -3518,12 +3518,12 @@  discard block
 block discarded – undo
3518 3518
                         ),
3519 3519
                         http_build_query($query_params['limit'])
3520 3520
                     );
3521
-                    throw new EE_Error($e . "|" . $e);
3521
+                    throw new EE_Error($e."|".$e);
3522 3522
                 }
3523 3523
                 // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3524
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3525
-            } elseif (! empty($query_params['limit'])) {
3526
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3524
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3525
+            } elseif ( ! empty($query_params['limit'])) {
3526
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3527 3527
             }
3528 3528
         }
3529 3529
         // set order by
@@ -3555,10 +3555,10 @@  discard block
 block discarded – undo
3555 3555
                 $order_array = [];
3556 3556
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3557 3557
                     $order         = $this->_extract_order($order);
3558
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3558
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3559 3559
                 }
3560
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3561
-            } elseif (! empty($query_params['order_by'])) {
3560
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3561
+            } elseif ( ! empty($query_params['order_by'])) {
3562 3562
                 $this->_extract_related_model_info_from_query_param(
3563 3563
                     $query_params['order_by'],
3564 3564
                     $query_object,
@@ -3569,7 +3569,7 @@  discard block
 block discarded – undo
3569 3569
                     ? $this->_extract_order($query_params['order'])
3570 3570
                     : 'DESC';
3571 3571
                 $query_object->set_order_by_sql(
3572
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3572
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3573 3573
                 );
3574 3574
             }
3575 3575
         }
@@ -3581,7 +3581,7 @@  discard block
 block discarded – undo
3581 3581
         ) {
3582 3582
             $pk_field = $this->get_primary_key_field();
3583 3583
             $order    = $this->_extract_order($query_params['order']);
3584
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3584
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3585 3585
         }
3586 3586
         // set group by
3587 3587
         if (array_key_exists('group_by', $query_params)) {
@@ -3591,10 +3591,10 @@  discard block
 block discarded – undo
3591 3591
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3592 3592
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3593 3593
                 }
3594
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3595
-            } elseif (! empty($query_params['group_by'])) {
3594
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3595
+            } elseif ( ! empty($query_params['group_by'])) {
3596 3596
                 $query_object->set_group_by_sql(
3597
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3597
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3598 3598
                 );
3599 3599
             }
3600 3600
         }
@@ -3604,7 +3604,7 @@  discard block
 block discarded – undo
3604 3604
         }
3605 3605
         // now, just verify they didn't pass anything wack
3606 3606
         foreach ($query_params as $query_key => $query_value) {
3607
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3607
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3608 3608
                 throw new EE_Error(
3609 3609
                     sprintf(
3610 3610
                         esc_html__(
@@ -3708,7 +3708,7 @@  discard block
 block discarded – undo
3708 3708
         $where_query_params = []
3709 3709
     ) {
3710 3710
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3711
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3711
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3712 3712
             throw new EE_Error(
3713 3713
                 sprintf(
3714 3714
                     esc_html__(
@@ -3738,7 +3738,7 @@  discard block
 block discarded – undo
3738 3738
                 // we don't want to add full or even minimum default where conditions from this model, so just continue
3739 3739
                 continue;
3740 3740
             }
3741
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3741
+            $overrides = $this->_override_defaults_or_make_null_friendly(
3742 3742
                 $related_model_universal_where_params,
3743 3743
                 $where_query_params,
3744 3744
                 $related_model,
@@ -3847,19 +3847,19 @@  discard block
 block discarded – undo
3847 3847
     ) {
3848 3848
         $null_friendly_where_conditions = [];
3849 3849
         $none_overridden                = true;
3850
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3850
+        $or_condition_key_for_defaults  = 'OR*'.get_class($model);
3851 3851
         foreach ($default_where_conditions as $key => $val) {
3852
-            if (isset($provided_where_conditions[ $key ])) {
3852
+            if (isset($provided_where_conditions[$key])) {
3853 3853
                 $none_overridden = false;
3854 3854
             } else {
3855
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3855
+                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3856 3856
             }
3857 3857
         }
3858 3858
         if ($none_overridden && $default_where_conditions) {
3859 3859
             if ($model->has_primary_key_field()) {
3860
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3860
+                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3861 3861
                                                                                    . "."
3862
-                                                                                   . $model->primary_key_name() ] =
3862
+                                                                                   . $model->primary_key_name()] =
3863 3863
                     ['IS NULL'];
3864 3864
             }/*else{
3865 3865
                 //@todo NO PK, use other defaults
@@ -3968,7 +3968,7 @@  discard block
 block discarded – undo
3968 3968
             foreach ($tables as $table_obj) {
3969 3969
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3970 3970
                                        . $table_obj->get_fully_qualified_pk_column();
3971
-                if (! in_array($qualified_pk_column, $selects)) {
3971
+                if ( ! in_array($qualified_pk_column, $selects)) {
3972 3972
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3973 3973
                 }
3974 3974
             }
@@ -4120,9 +4120,9 @@  discard block
 block discarded – undo
4120 4120
         $query_parameter_type
4121 4121
     ) {
4122 4122
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4123
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4123
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4124 4124
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4125
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4125
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4126 4126
                 if ($possible_join_string === '') {
4127 4127
                     // nothing left to $query_param
4128 4128
                     // we should actually end in a field name, not a model like this!
@@ -4254,7 +4254,7 @@  discard block
 block discarded – undo
4254 4254
     {
4255 4255
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4256 4256
         if ($SQL) {
4257
-            return " WHERE " . $SQL;
4257
+            return " WHERE ".$SQL;
4258 4258
         }
4259 4259
         return '';
4260 4260
     }
@@ -4272,7 +4272,7 @@  discard block
 block discarded – undo
4272 4272
     {
4273 4273
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4274 4274
         if ($SQL) {
4275
-            return " HAVING " . $SQL;
4275
+            return " HAVING ".$SQL;
4276 4276
         }
4277 4277
         return '';
4278 4278
     }
@@ -4295,7 +4295,7 @@  discard block
 block discarded – undo
4295 4295
             $query_param =
4296 4296
                 $this->_remove_stars_and_anything_after_from_condition_query_param_key(
4297 4297
                     $query_param
4298
-                );// str_replace("*",'',$query_param);
4298
+                ); // str_replace("*",'',$query_param);
4299 4299
             if (in_array($query_param, $this->_logic_query_param_keys)) {
4300 4300
                 switch ($query_param) {
4301 4301
                     case 'not':
@@ -4329,7 +4329,7 @@  discard block
 block discarded – undo
4329 4329
             } else {
4330 4330
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4331 4331
                 // if it's not a normal field, maybe it's a custom selection?
4332
-                if (! $field_obj) {
4332
+                if ( ! $field_obj) {
4333 4333
                     if ($this->_custom_selections instanceof CustomSelects) {
4334 4334
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4335 4335
                     } else {
@@ -4345,7 +4345,7 @@  discard block
 block discarded – undo
4345 4345
                     }
4346 4346
                 }
4347 4347
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4348
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4348
+                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4349 4349
             }
4350 4350
         }
4351 4351
         return $where_clauses ? implode($glue, $where_clauses) : '';
@@ -4367,7 +4367,7 @@  discard block
 block discarded – undo
4367 4367
                 $field->get_model_name(),
4368 4368
                 $query_param
4369 4369
             );
4370
-            return $table_alias_prefix . $field->get_qualified_column();
4370
+            return $table_alias_prefix.$field->get_qualified_column();
4371 4371
         }
4372 4372
         if (
4373 4373
             $this->_custom_selections instanceof CustomSelects
@@ -4424,7 +4424,7 @@  discard block
 block discarded – undo
4424 4424
     {
4425 4425
         if (is_array($op_and_value)) {
4426 4426
             $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4427
-            if (! $operator) {
4427
+            if ( ! $operator) {
4428 4428
                 $php_array_like_string = [];
4429 4429
                 foreach ($op_and_value as $key => $value) {
4430 4430
                     $php_array_like_string[] = "$key=>$value";
@@ -4446,14 +4446,14 @@  discard block
 block discarded – undo
4446 4446
         }
4447 4447
         // check to see if the value is actually another field
4448 4448
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4449
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4449
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4450 4450
         }
4451 4451
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4452 4452
             // in this case, the value should be an array, or at least a comma-separated list
4453 4453
             // it will need to handle a little differently
4454 4454
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4455 4455
             // note: $cleaned_value has already been run through $wpdb->prepare()
4456
-            return $operator . SP . $cleaned_value;
4456
+            return $operator.SP.$cleaned_value;
4457 4457
         }
4458 4458
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4459 4459
             // the value should be an array with count of two.
@@ -4469,7 +4469,7 @@  discard block
 block discarded – undo
4469 4469
                 );
4470 4470
             }
4471 4471
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4472
-            return $operator . SP . $cleaned_value;
4472
+            return $operator.SP.$cleaned_value;
4473 4473
         }
4474 4474
         if (in_array($operator, $this->valid_null_style_operators())) {
4475 4475
             if ($value !== null) {
@@ -4489,10 +4489,10 @@  discard block
 block discarded – undo
4489 4489
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4490 4490
             // if the operator is 'LIKE', we want to allow percent signs (%) and not
4491 4491
             // remove other junk. So just treat it as a string.
4492
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4492
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4493 4493
         }
4494
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4495
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4494
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4495
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4496 4496
         }
4497 4497
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4498 4498
             throw new EE_Error(
@@ -4506,7 +4506,7 @@  discard block
 block discarded – undo
4506 4506
                 )
4507 4507
             );
4508 4508
         }
4509
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4509
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4510 4510
             throw new EE_Error(
4511 4511
                 sprintf(
4512 4512
                     esc_html__(
@@ -4545,7 +4545,7 @@  discard block
 block discarded – undo
4545 4545
         foreach ($values as $value) {
4546 4546
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4547 4547
         }
4548
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4548
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4549 4549
     }
4550 4550
 
4551 4551
 
@@ -4585,7 +4585,7 @@  discard block
 block discarded – undo
4585 4585
                                 . $main_table->get_table_name()
4586 4586
                                 . " WHERE FALSE";
4587 4587
         }
4588
-        return "(" . implode(",", $cleaned_values) . ")";
4588
+        return "(".implode(",", $cleaned_values).")";
4589 4589
     }
4590 4590
 
4591 4591
 
@@ -4604,7 +4604,7 @@  discard block
 block discarded – undo
4604 4604
                 $this->_prepare_value_for_use_in_db($value, $field_obj)
4605 4605
             );
4606 4606
         } //$field_obj should really just be a data type
4607
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4607
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4608 4608
             throw new EE_Error(
4609 4609
                 sprintf(
4610 4610
                     esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4641,14 +4641,14 @@  discard block
 block discarded – undo
4641 4641
             );
4642 4642
         }
4643 4643
         $number_of_parts       = count($query_param_parts);
4644
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4644
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4645 4645
         if ($number_of_parts === 1) {
4646 4646
             $field_name = $last_query_param_part;
4647 4647
             $model_obj  = $this;
4648 4648
         } else {// $number_of_parts >= 2
4649 4649
             // the last part is the column name, and there are only 2parts. therefore...
4650 4650
             $field_name = $last_query_param_part;
4651
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4651
+            $model_obj  = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4652 4652
         }
4653 4653
         try {
4654 4654
             return $model_obj->field_settings_for($field_name);
@@ -4669,7 +4669,7 @@  discard block
 block discarded – undo
4669 4669
     public function _get_qualified_column_for_field($field_name)
4670 4670
     {
4671 4671
         $all_fields = $this->field_settings();
4672
-        $field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4672
+        $field      = isset($all_fields[$field_name]) ? $all_fields[$field_name] : false;
4673 4673
         if ($field) {
4674 4674
             return $field->get_qualified_column();
4675 4675
         }
@@ -4739,10 +4739,10 @@  discard block
 block discarded – undo
4739 4739
      */
4740 4740
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4741 4741
     {
4742
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4742
+        $table_prefix      = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain) ? '' : '__');
4743 4743
         $qualified_columns = [];
4744 4744
         foreach ($this->field_settings() as $field_name => $field) {
4745
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4745
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4746 4746
         }
4747 4747
         return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4748 4748
     }
@@ -4766,11 +4766,11 @@  discard block
 block discarded – undo
4766 4766
             if ($table_obj instanceof EE_Primary_Table) {
4767 4767
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4768 4768
                     ? $table_obj->get_select_join_limit($limit)
4769
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4769
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4770 4770
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4771 4771
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4772 4772
                     ? $table_obj->get_select_join_limit_join($limit)
4773
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4773
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4774 4774
             }
4775 4775
         }
4776 4776
         return $SQL;
@@ -4840,7 +4840,7 @@  discard block
 block discarded – undo
4840 4840
         foreach ($this->field_settings() as $field_obj) {
4841 4841
             // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4842 4842
             /** @var $field_obj EE_Model_Field_Base */
4843
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4843
+            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4844 4844
         }
4845 4845
         return $data_types;
4846 4846
     }
@@ -4855,8 +4855,8 @@  discard block
 block discarded – undo
4855 4855
      */
4856 4856
     public function get_related_model_obj($model_name)
4857 4857
     {
4858
-        $model_classname = "EEM_" . $model_name;
4859
-        if (! class_exists($model_classname)) {
4858
+        $model_classname = "EEM_".$model_name;
4859
+        if ( ! class_exists($model_classname)) {
4860 4860
             throw new EE_Error(
4861 4861
                 sprintf(
4862 4862
                     esc_html__(
@@ -4868,7 +4868,7 @@  discard block
 block discarded – undo
4868 4868
                 )
4869 4869
             );
4870 4870
         }
4871
-        return call_user_func($model_classname . "::instance");
4871
+        return call_user_func($model_classname."::instance");
4872 4872
     }
4873 4873
 
4874 4874
 
@@ -4895,7 +4895,7 @@  discard block
 block discarded – undo
4895 4895
         $belongs_to_relations = [];
4896 4896
         foreach ($this->relation_settings() as $model_name => $relation_obj) {
4897 4897
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
4898
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4898
+                $belongs_to_relations[$model_name] = $relation_obj;
4899 4899
             }
4900 4900
         }
4901 4901
         return $belongs_to_relations;
@@ -4912,7 +4912,7 @@  discard block
 block discarded – undo
4912 4912
     public function related_settings_for($relation_name)
4913 4913
     {
4914 4914
         $relatedModels = $this->relation_settings();
4915
-        if (! array_key_exists($relation_name, $relatedModels)) {
4915
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
4916 4916
             throw new EE_Error(
4917 4917
                 sprintf(
4918 4918
                     esc_html__(
@@ -4925,7 +4925,7 @@  discard block
 block discarded – undo
4925 4925
                 )
4926 4926
             );
4927 4927
         }
4928
-        return $relatedModels[ $relation_name ];
4928
+        return $relatedModels[$relation_name];
4929 4929
     }
4930 4930
 
4931 4931
 
@@ -4941,7 +4941,7 @@  discard block
 block discarded – undo
4941 4941
     public function field_settings_for($fieldName, $include_db_only_fields = true)
4942 4942
     {
4943 4943
         $fieldSettings = $this->field_settings($include_db_only_fields);
4944
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4944
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
4945 4945
             throw new EE_Error(
4946 4946
                 sprintf(
4947 4947
                     esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
@@ -4950,7 +4950,7 @@  discard block
 block discarded – undo
4950 4950
                 )
4951 4951
             );
4952 4952
         }
4953
-        return $fieldSettings[ $fieldName ];
4953
+        return $fieldSettings[$fieldName];
4954 4954
     }
4955 4955
 
4956 4956
 
@@ -4963,7 +4963,7 @@  discard block
 block discarded – undo
4963 4963
     public function has_field($fieldName)
4964 4964
     {
4965 4965
         $fieldSettings = $this->field_settings(true);
4966
-        if (isset($fieldSettings[ $fieldName ])) {
4966
+        if (isset($fieldSettings[$fieldName])) {
4967 4967
             return true;
4968 4968
         }
4969 4969
         return false;
@@ -4979,7 +4979,7 @@  discard block
 block discarded – undo
4979 4979
     public function has_relation($relation_name)
4980 4980
     {
4981 4981
         $relations = $this->relation_settings();
4982
-        if (isset($relations[ $relation_name ])) {
4982
+        if (isset($relations[$relation_name])) {
4983 4983
             return true;
4984 4984
         }
4985 4985
         return false;
@@ -5015,7 +5015,7 @@  discard block
 block discarded – undo
5015 5015
                     break;
5016 5016
                 }
5017 5017
             }
5018
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5018
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5019 5019
                 throw new EE_Error(
5020 5020
                     sprintf(
5021 5021
                         esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
@@ -5075,17 +5075,17 @@  discard block
 block discarded – undo
5075 5075
      */
5076 5076
     public function get_foreign_key_to($model_name)
5077 5077
     {
5078
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5078
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5079 5079
             foreach ($this->field_settings() as $field) {
5080 5080
                 if (
5081 5081
                     $field instanceof EE_Foreign_Key_Field_Base
5082 5082
                     && in_array($model_name, $field->get_model_names_pointed_to())
5083 5083
                 ) {
5084
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5084
+                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
5085 5085
                     break;
5086 5086
                 }
5087 5087
             }
5088
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5088
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5089 5089
                 throw new EE_Error(
5090 5090
                     sprintf(
5091 5091
                         esc_html__(
@@ -5098,7 +5098,7 @@  discard block
 block discarded – undo
5098 5098
                 );
5099 5099
             }
5100 5100
         }
5101
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5101
+        return $this->_cache_foreign_key_to_fields[$model_name];
5102 5102
     }
5103 5103
 
5104 5104
 
@@ -5114,7 +5114,7 @@  discard block
 block discarded – undo
5114 5114
     {
5115 5115
         $table_alias_sans_model_relation_chain_prefix =
5116 5116
             EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5117
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5117
+        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
5118 5118
     }
5119 5119
 
5120 5120
 
@@ -5132,7 +5132,7 @@  discard block
 block discarded – undo
5132 5132
                 $this->_cached_fields = [];
5133 5133
                 foreach ($this->_fields as $fields_corresponding_to_table) {
5134 5134
                     foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5135
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5135
+                        $this->_cached_fields[$field_name] = $field_obj;
5136 5136
                     }
5137 5137
                 }
5138 5138
             }
@@ -5143,8 +5143,8 @@  discard block
 block discarded – undo
5143 5143
             foreach ($this->_fields as $fields_corresponding_to_table) {
5144 5144
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5145 5145
                     /** @var $field_obj EE_Model_Field_Base */
5146
-                    if (! $field_obj->is_db_only_field()) {
5147
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5146
+                    if ( ! $field_obj->is_db_only_field()) {
5147
+                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5148 5148
                     }
5149 5149
                 }
5150 5150
             }
@@ -5172,7 +5172,7 @@  discard block
 block discarded – undo
5172 5172
         $count_if_model_has_no_primary_key = 0;
5173 5173
         $has_primary_key                   = $this->has_primary_key_field();
5174 5174
         $primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5175
-        foreach ((array)$rows as $row) {
5175
+        foreach ((array) $rows as $row) {
5176 5176
             if (empty($row)) {
5177 5177
                 // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5178 5178
                 return [];
@@ -5185,12 +5185,12 @@  discard block
 block discarded – undo
5185 5185
                     $primary_key_field->get_qualified_column(),
5186 5186
                     $primary_key_field->get_table_column()
5187 5187
                 );
5188
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5188
+                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5189 5189
                     continue;
5190 5190
                 }
5191 5191
             }
5192 5192
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5193
-            if (! $classInstance) {
5193
+            if ( ! $classInstance) {
5194 5194
                 throw new EE_Error(
5195 5195
                     sprintf(
5196 5196
                         esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5203,7 +5203,7 @@  discard block
 block discarded – undo
5203 5203
             $classInstance->set_timezone($this->_timezone);
5204 5204
             // make sure if there is any timezone setting present that we set the timezone for the object
5205 5205
             $key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5206
-            $array_of_objects[ $key ] = $classInstance;
5206
+            $array_of_objects[$key] = $classInstance;
5207 5207
             // also, for all the relations of type BelongsTo, see if we can cache
5208 5208
             // those related models
5209 5209
             // (we could do this for other relations too, but if there are conditions
@@ -5247,9 +5247,9 @@  discard block
 block discarded – undo
5247 5247
         $results = [];
5248 5248
         if ($this->_custom_selections instanceof CustomSelects) {
5249 5249
             foreach ($this->_custom_selections->columnAliases() as $alias) {
5250
-                if (isset($db_results_row[ $alias ])) {
5251
-                    $results[ $alias ] = $this->convertValueToDataType(
5252
-                        $db_results_row[ $alias ],
5250
+                if (isset($db_results_row[$alias])) {
5251
+                    $results[$alias] = $this->convertValueToDataType(
5252
+                        $db_results_row[$alias],
5253 5253
                         $this->_custom_selections->getDataTypeForAlias($alias)
5254 5254
                     );
5255 5255
                 }
@@ -5270,11 +5270,11 @@  discard block
 block discarded – undo
5270 5270
     {
5271 5271
         switch ($datatype) {
5272 5272
             case '%f':
5273
-                return (float)$value;
5273
+                return (float) $value;
5274 5274
             case '%d':
5275
-                return (int)$value;
5275
+                return (int) $value;
5276 5276
             default:
5277
-                return (string)$value;
5277
+                return (string) $value;
5278 5278
         }
5279 5279
     }
5280 5280
 
@@ -5294,7 +5294,7 @@  discard block
 block discarded – undo
5294 5294
         $this_model_fields_and_values = [];
5295 5295
         // setup the row using default values;
5296 5296
         foreach ($this->field_settings() as $field_name => $field_obj) {
5297
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5297
+            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5298 5298
         }
5299 5299
         $className = $this->_get_class_name();
5300 5300
         return EE_Registry::instance()->load_class(
@@ -5315,20 +5315,20 @@  discard block
 block discarded – undo
5315 5315
      */
5316 5316
     public function instantiate_class_from_array_or_object($cols_n_values)
5317 5317
     {
5318
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5318
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5319 5319
             $cols_n_values = get_object_vars($cols_n_values);
5320 5320
         }
5321 5321
         $primary_key = null;
5322 5322
         // make sure the array only has keys that are fields/columns on this model
5323 5323
         $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5324
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5325
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5324
+        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5325
+            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5326 5326
         }
5327 5327
         $className = $this->_get_class_name();
5328 5328
         // check we actually found results that we can use to build our model object
5329 5329
         // if not, return null
5330 5330
         if ($this->has_primary_key_field()) {
5331
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5331
+            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5332 5332
                 return null;
5333 5333
             }
5334 5334
         } elseif ($this->unique_indexes()) {
@@ -5340,7 +5340,7 @@  discard block
 block discarded – undo
5340 5340
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5341 5341
         if ($primary_key) {
5342 5342
             $classInstance = $this->get_from_entity_map($primary_key);
5343
-            if (! $classInstance) {
5343
+            if ( ! $classInstance) {
5344 5344
                 $classInstance = EE_Registry::instance()->load_class(
5345 5345
                     $className,
5346 5346
                     [$this_model_fields_n_values, $this->_timezone],
@@ -5370,8 +5370,8 @@  discard block
 block discarded – undo
5370 5370
      */
5371 5371
     public function get_from_entity_map($id)
5372 5372
     {
5373
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5374
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5373
+        return isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])
5374
+            ? $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] : null;
5375 5375
     }
5376 5376
 
5377 5377
 
@@ -5394,7 +5394,7 @@  discard block
 block discarded – undo
5394 5394
     public function add_to_entity_map(EE_Base_Class $object)
5395 5395
     {
5396 5396
         $className = $this->_get_class_name();
5397
-        if (! $object instanceof $className) {
5397
+        if ( ! $object instanceof $className) {
5398 5398
             throw new EE_Error(
5399 5399
                 sprintf(
5400 5400
                     esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
@@ -5403,7 +5403,7 @@  discard block
 block discarded – undo
5403 5403
                 )
5404 5404
             );
5405 5405
         }
5406
-        if (! $object->ID()) {
5406
+        if ( ! $object->ID()) {
5407 5407
             throw new EE_Error(
5408 5408
                 sprintf(
5409 5409
                     esc_html__(
@@ -5419,7 +5419,7 @@  discard block
 block discarded – undo
5419 5419
         if ($classInstance) {
5420 5420
             return $classInstance;
5421 5421
         }
5422
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5422
+        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5423 5423
         return $object;
5424 5424
     }
5425 5425
 
@@ -5434,11 +5434,11 @@  discard block
 block discarded – undo
5434 5434
     public function clear_entity_map($id = null)
5435 5435
     {
5436 5436
         if (empty($id)) {
5437
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5437
+            $this->_entity_map[EEM_Base::$_model_query_blog_id] = [];
5438 5438
             return true;
5439 5439
         }
5440
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5441
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5440
+        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5441
+            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5442 5442
             return true;
5443 5443
         }
5444 5444
         return false;
@@ -5479,18 +5479,18 @@  discard block
 block discarded – undo
5479 5479
             // there is a primary key on this table and its not set. Use defaults for all its columns
5480 5480
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5481 5481
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5482
-                    if (! $field_obj->is_db_only_field()) {
5482
+                    if ( ! $field_obj->is_db_only_field()) {
5483 5483
                         // prepare field as if its coming from db
5484 5484
                         $prepared_value                            =
5485 5485
                             $field_obj->prepare_for_set($field_obj->get_default_value());
5486
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5486
+                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5487 5487
                     }
5488 5488
                 }
5489 5489
             } else {
5490 5490
                 // the table's rows existed. Use their values
5491 5491
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5492
-                    if (! $field_obj->is_db_only_field()) {
5493
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5492
+                    if ( ! $field_obj->is_db_only_field()) {
5493
+                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5494 5494
                             $cols_n_values,
5495 5495
                             $field_obj->get_qualified_column(),
5496 5496
                             $field_obj->get_table_column()
@@ -5515,17 +5515,17 @@  discard block
 block discarded – undo
5515 5515
         // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5516 5516
         // does the field on the model relate to this column retrieved from the db?
5517 5517
         // or is it a db-only field? (not relating to the model)
5518
-        if (isset($cols_n_values[ $qualified_column ])) {
5519
-            $value = $cols_n_values[ $qualified_column ];
5520
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5521
-            $value = $cols_n_values[ $regular_column ];
5522
-        } elseif (! empty($this->foreign_key_aliases)) {
5518
+        if (isset($cols_n_values[$qualified_column])) {
5519
+            $value = $cols_n_values[$qualified_column];
5520
+        } elseif (isset($cols_n_values[$regular_column])) {
5521
+            $value = $cols_n_values[$regular_column];
5522
+        } elseif ( ! empty($this->foreign_key_aliases)) {
5523 5523
             // no PK?  ok check if there is a foreign key alias set for this table
5524 5524
             // then check if that alias exists in the incoming data
5525 5525
             // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5526 5526
             foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5527
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5528
-                    $value = $cols_n_values[ $FK_alias ];
5527
+                if ($PK_column === $qualified_column && isset($cols_n_values[$FK_alias])) {
5528
+                    $value = $cols_n_values[$FK_alias];
5529 5529
                     break;
5530 5530
                 }
5531 5531
             }
@@ -5561,7 +5561,7 @@  discard block
 block discarded – undo
5561 5561
                     $obj_in_map->clear_cache($relation_name, null, true);
5562 5562
                 }
5563 5563
             }
5564
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5564
+            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5565 5565
             return $obj_in_map;
5566 5566
         }
5567 5567
         return $this->get_one_by_ID($id);
@@ -5613,7 +5613,7 @@  discard block
 block discarded – undo
5613 5613
      */
5614 5614
     private function _get_class_name()
5615 5615
     {
5616
-        return "EE_" . $this->get_this_model_name();
5616
+        return "EE_".$this->get_this_model_name();
5617 5617
     }
5618 5618
 
5619 5619
 
@@ -5627,7 +5627,7 @@  discard block
 block discarded – undo
5627 5627
      */
5628 5628
     public function item_name($quantity = 1)
5629 5629
     {
5630
-        return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5630
+        return (int) $quantity === 1 ? $this->singular_item : $this->plural_item;
5631 5631
     }
5632 5632
 
5633 5633
 
@@ -5659,7 +5659,7 @@  discard block
 block discarded – undo
5659 5659
     {
5660 5660
         $className = get_class($this);
5661 5661
         $tagName   = "FHEE__{$className}__{$methodName}";
5662
-        if (! has_filter($tagName)) {
5662
+        if ( ! has_filter($tagName)) {
5663 5663
             throw new EE_Error(
5664 5664
                 sprintf(
5665 5665
                     esc_html__(
@@ -5829,7 +5829,7 @@  discard block
 block discarded – undo
5829 5829
         $unique_indexes = [];
5830 5830
         foreach ($this->_indexes as $name => $index) {
5831 5831
             if ($index instanceof EE_Unique_Index) {
5832
-                $unique_indexes [ $name ] = $index;
5832
+                $unique_indexes [$name] = $index;
5833 5833
             }
5834 5834
         }
5835 5835
         return $unique_indexes;
@@ -5893,7 +5893,7 @@  discard block
 block discarded – undo
5893 5893
         $key_values_in_combined_pk = [];
5894 5894
         parse_str($index_primary_key_string, $key_values_in_combined_pk);
5895 5895
         foreach ($key_fields as $key_field_name => $field_obj) {
5896
-            if (! isset($key_values_in_combined_pk[ $key_field_name ])) {
5896
+            if ( ! isset($key_values_in_combined_pk[$key_field_name])) {
5897 5897
                 return null;
5898 5898
             }
5899 5899
         }
@@ -5913,7 +5913,7 @@  discard block
 block discarded – undo
5913 5913
     {
5914 5914
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5915 5915
         foreach ($keys_it_should_have as $key) {
5916
-            if (! isset($key_values[ $key ])) {
5916
+            if ( ! isset($key_values[$key])) {
5917 5917
                 return false;
5918 5918
             }
5919 5919
         }
@@ -5952,8 +5952,8 @@  discard block
 block discarded – undo
5952 5952
         }
5953 5953
         // even copies obviously won't have the same ID, so remove the primary key
5954 5954
         // from the WHERE conditions for finding copies (if there is a primary key, of course)
5955
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5956
-            unset($attributes_array[ $this->primary_key_name() ]);
5955
+        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
5956
+            unset($attributes_array[$this->primary_key_name()]);
5957 5957
         }
5958 5958
         if (isset($query_params[0])) {
5959 5959
             $query_params[0] = array_merge($attributes_array, $query_params);
@@ -5975,7 +5975,7 @@  discard block
 block discarded – undo
5975 5975
      */
5976 5976
     public function get_one_copy($model_object_or_attributes_array, $query_params = [])
5977 5977
     {
5978
-        if (! is_array($query_params)) {
5978
+        if ( ! is_array($query_params)) {
5979 5979
             EE_Error::doing_it_wrong(
5980 5980
                 'EEM_Base::get_one_copy',
5981 5981
                 sprintf(
@@ -6025,7 +6025,7 @@  discard block
 block discarded – undo
6025 6025
     private function _prepare_operator_for_sql($operator_supplied)
6026 6026
     {
6027 6027
         $sql_operator =
6028
-            isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6028
+            isset($this->_valid_operators[$operator_supplied]) ? $this->_valid_operators[$operator_supplied]
6029 6029
                 : null;
6030 6030
         if ($sql_operator) {
6031 6031
             return $sql_operator;
@@ -6124,7 +6124,7 @@  discard block
 block discarded – undo
6124 6124
         $objs  = $this->get_all($query_params);
6125 6125
         $names = [];
6126 6126
         foreach ($objs as $obj) {
6127
-            $names[ $obj->ID() ] = $obj->name();
6127
+            $names[$obj->ID()] = $obj->name();
6128 6128
         }
6129 6129
         return $names;
6130 6130
     }
@@ -6145,7 +6145,7 @@  discard block
 block discarded – undo
6145 6145
      */
6146 6146
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
6147 6147
     {
6148
-        if (! $this->has_primary_key_field()) {
6148
+        if ( ! $this->has_primary_key_field()) {
6149 6149
             if (WP_DEBUG) {
6150 6150
                 EE_Error::add_error(
6151 6151
                     esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -6158,7 +6158,7 @@  discard block
 block discarded – undo
6158 6158
         $IDs = [];
6159 6159
         foreach ($model_objects as $model_object) {
6160 6160
             $id = $model_object->ID();
6161
-            if (! $id) {
6161
+            if ( ! $id) {
6162 6162
                 if ($filter_out_empty_ids) {
6163 6163
                     continue;
6164 6164
                 }
@@ -6208,22 +6208,22 @@  discard block
 block discarded – undo
6208 6208
         EEM_Base::verify_is_valid_cap_context($context);
6209 6209
         // check if we ought to run the restriction generator first
6210 6210
         if (
6211
-            isset($this->_cap_restriction_generators[ $context ])
6212
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6213
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6211
+            isset($this->_cap_restriction_generators[$context])
6212
+            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6213
+            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6214 6214
         ) {
6215
-            $this->_cap_restrictions[ $context ] = array_merge(
6216
-                $this->_cap_restrictions[ $context ],
6217
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6215
+            $this->_cap_restrictions[$context] = array_merge(
6216
+                $this->_cap_restrictions[$context],
6217
+                $this->_cap_restriction_generators[$context]->generate_restrictions()
6218 6218
             );
6219 6219
         }
6220 6220
         // and make sure we've finalized the construction of each restriction
6221
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6221
+        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6222 6222
             if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6223 6223
                 $where_conditions_obj->_finalize_construct($this);
6224 6224
             }
6225 6225
         }
6226
-        return $this->_cap_restrictions[ $context ];
6226
+        return $this->_cap_restrictions[$context];
6227 6227
     }
6228 6228
 
6229 6229
 
@@ -6253,9 +6253,9 @@  discard block
 block discarded – undo
6253 6253
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6254 6254
             if (
6255 6255
             ! EE_Capabilities::instance()
6256
-                             ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6256
+                             ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6257 6257
             ) {
6258
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6258
+                $missing_caps[$cap] = $restriction_if_no_cap;
6259 6259
             }
6260 6260
         }
6261 6261
         return $missing_caps;
@@ -6288,8 +6288,8 @@  discard block
 block discarded – undo
6288 6288
     public function cap_action_for_context($context)
6289 6289
     {
6290 6290
         $mapping = $this->cap_contexts_to_cap_action_map();
6291
-        if (isset($mapping[ $context ])) {
6292
-            return $mapping[ $context ];
6291
+        if (isset($mapping[$context])) {
6292
+            return $mapping[$context];
6293 6293
         }
6294 6294
         if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6295 6295
             return $action;
@@ -6407,7 +6407,7 @@  discard block
 block discarded – undo
6407 6407
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6408 6408
             if (
6409 6409
                 $query_param_key === $logic_query_param_key
6410
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6410
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6411 6411
             ) {
6412 6412
                 return true;
6413 6413
             }
@@ -6465,7 +6465,7 @@  discard block
 block discarded – undo
6465 6465
         if ($password_field instanceof EE_Password_Field) {
6466 6466
             $field_names = $password_field->protectedFields();
6467 6467
             foreach ($field_names as $field_name) {
6468
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6468
+                $fields[$field_name] = $this->field_settings_for($field_name);
6469 6469
             }
6470 6470
         }
6471 6471
         return $fields;
@@ -6491,7 +6491,7 @@  discard block
 block discarded – undo
6491 6491
         if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6492 6492
             $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6493 6493
         }
6494
-        if (! is_array($model_obj_or_fields_n_values)) {
6494
+        if ( ! is_array($model_obj_or_fields_n_values)) {
6495 6495
             throw new UnexpectedEntityException(
6496 6496
                 $model_obj_or_fields_n_values,
6497 6497
                 'EE_Base_Class',
@@ -6571,7 +6571,7 @@  discard block
 block discarded – undo
6571 6571
                 )
6572 6572
             );
6573 6573
         }
6574
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6574
+        return ($this->model_chain_to_password ? $this->model_chain_to_password.'.' : '').$password_field_name;
6575 6575
     }
6576 6576
 
6577 6577
 
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 3 patches
Doc Comments   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -411,7 +411,7 @@  discard block
 block discarded – undo
411 411
      * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
412 412
      *
413 413
      * @param string $model_classname
414
-     * @param null   $timezone
414
+     * @param string|null   $timezone
415 415
      * @return EEM_Base
416 416
      * @throws ReflectionException
417 417
      * @throws InvalidArgumentException
@@ -581,7 +581,7 @@  discard block
 block discarded – undo
581 581
      *
582 582
      * @param EE_Datetime_Field $datetime_field
583 583
      * @param bool              $pretty
584
-     * @param null              $date_or_time
584
+     * @param string|null              $date_or_time
585 585
      * @return void
586 586
      * @throws InvalidArgumentException
587 587
      * @throws InvalidInterfaceException
@@ -667,7 +667,7 @@  discard block
 block discarded – undo
667 667
      *
668 668
      * @param array  $props_n_values    incoming array of properties and their values
669 669
      * @param string $classname         the classname of the child class
670
-     * @param null   $timezone
670
+     * @param string|null   $timezone
671 671
      * @param array  $date_formats      incoming date_formats in an array where the first value is the
672 672
      *                                  date_format and the second value is the time format
673 673
      * @return mixed (EE_Base_Class|bool)
@@ -1536,7 +1536,7 @@  discard block
 block discarded – undo
1536 1536
      *
1537 1537
      * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
1538 1538
      *                             where the first value is the date format and the second value is the time format.
1539
-     * @return mixed string|array
1539
+     * @return string string|array
1540 1540
      */
1541 1541
     public function get_format($full = true)
1542 1542
     {
@@ -1659,7 +1659,7 @@  discard block
 block discarded – undo
1659 1659
      *
1660 1660
      * @param null  $field_to_order_by  What field is being used as the reference point.
1661 1661
      * @param array $query_params       Any additional conditions on the query.
1662
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1662
+     * @param string  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1663 1663
      *                                  you can indicate just the columns you want returned
1664 1664
      * @return array|EE_Base_Class
1665 1665
      * @throws ReflectionException
@@ -1687,7 +1687,7 @@  discard block
 block discarded – undo
1687 1687
      *
1688 1688
      * @param null  $field_to_order_by  What field is being used as the reference point.
1689 1689
      * @param array $query_params       Any additional conditions on the query.
1690
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1690
+     * @param string  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1691 1691
      *                                  you can indicate just the column you want returned
1692 1692
      * @return array|EE_Base_Class
1693 1693
      * @throws ReflectionException
@@ -2137,7 +2137,7 @@  discard block
 block discarded – undo
2137 2137
      * Deletes this model object permanently from db
2138 2138
      * (but keep in mind related models may block the delete and return an error)
2139 2139
      *
2140
-     * @return bool | int
2140
+     * @return integer | int
2141 2141
      * @throws ReflectionException
2142 2142
      * @throws InvalidArgumentException
2143 2143
      * @throws InvalidInterfaceException
@@ -2570,7 +2570,7 @@  discard block
 block discarded – undo
2570 2570
      * @param string $field_to_sum  name of field to count by.
2571 2571
      *                              By default, uses primary key
2572 2572
      *                              (which doesn't make much sense, so you should probably change it)
2573
-     * @return int
2573
+     * @return double
2574 2574
      * @throws ReflectionException
2575 2575
      * @throws InvalidArgumentException
2576 2576
      * @throws InvalidInterfaceException
@@ -3206,7 +3206,7 @@  discard block
 block discarded – undo
3206 3206
      * sets the time on a datetime property
3207 3207
      *
3208 3208
      * @access protected
3209
-     * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
3209
+     * @param string $time       a valid time string for php datetime functions (or DateTime object)
3210 3210
      * @param string          $field_name the name of the field the time is being set on (must match a
3211 3211
      *                                    EE_Datetime_Field)
3212 3212
      * @throws InvalidArgumentException
@@ -3301,7 +3301,7 @@  discard block
 block discarded – undo
3301 3301
      * sets the date on a datetime property
3302 3302
      *
3303 3303
      * @access protected
3304
-     * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
3304
+     * @param string $date       a valid date string for php datetime functions ( or DateTime object)
3305 3305
      * @param string          $field_name the name of the field the date is being set on (must match a
3306 3306
      *                                    EE_Datetime_Field)
3307 3307
      * @throws InvalidArgumentException
Please login to merge, or discard this patch.
Indentation   +3317 added lines, -3317 removed lines patch added patch discarded remove patch
@@ -13,3332 +13,3332 @@
 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   $fieldValues                             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 boolean $by_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($fieldValues = [], $by_db = false, $timezone = '', $date_formats = [])
141
-    {
142
-        $className = get_class($this);
143
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
144
-        $this->_model = $this->get_model();
145
-        $model_fields = $this->_model->field_settings();
146
-        // ensure $fieldValues is an array
147
-        $fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
148
-        // verify client code has not passed any invalid field names
149
-        foreach ($fieldValues as $field_name => $field_value) {
150
-            if (! isset($model_fields[ $field_name ])) {
151
-                throw new EE_Error(
152
-                    sprintf(
153
-                        esc_html__(
154
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
155
-                            'event_espresso'
156
-                        ),
157
-                        $field_name,
158
-                        get_class($this),
159
-                        implode(', ', array_keys($model_fields))
160
-                    )
161
-                );
162
-            }
163
-        }
164
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
165
-        if (! empty($date_formats) && is_array($date_formats)) {
166
-            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
167
-        } else {
168
-            // set default formats for date and time
169
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
170
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
171
-        }
172
-        // if db model is instantiating
173
-        if ($by_db) {
174
-            // client code has indicated these field values are from the database
175
-            foreach ($model_fields as $fieldName => $field) {
176
-                $this->set_from_db(
177
-                    $fieldName,
178
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
179
-                );
180
-            }
181
-        } else {
182
-            // we're constructing a brand
183
-            // new instance of the model object. Generally, this means we'll need to do more field validation
184
-            foreach ($model_fields as $fieldName => $field) {
185
-                $this->set(
186
-                    $fieldName,
187
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
188
-                    true
189
-                );
190
-            }
191
-        }
192
-        // remember what values were passed to this constructor
193
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
194
-        // remember in entity mapper
195
-        if (! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
196
-            $this->_model->add_to_entity_map($this);
197
-        }
198
-        // setup all the relations
199
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
200
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
-                $this->_model_relations[ $relation_name ] = null;
202
-            } else {
203
-                $this->_model_relations[ $relation_name ] = [];
204
-            }
205
-        }
206
-        /**
207
-         * Action done at the end of each model object construction
208
-         *
209
-         * @param EE_Base_Class $this the model object just created
210
-         */
211
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
-    }
213
-
214
-
215
-    /**
216
-     * for getting a model while instantiated.
217
-     *
218
-     * @return EEM_Base | EEM_CPT_Base
219
-     * @throws ReflectionException
220
-     * @throws InvalidArgumentException
221
-     * @throws InvalidInterfaceException
222
-     * @throws InvalidDataTypeException
223
-     * @throws EE_Error
224
-     */
225
-    public function get_model()
226
-    {
227
-        if (! $this->_model) {
228
-            $modelName    = self::_get_model_classname(get_class($this));
229
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
230
-        }
231
-        return $this->_model;
232
-    }
233
-
234
-
235
-    /**
236
-     * Overrides parent because parent expects old models.
237
-     * This also doesn't do any validation, and won't work for serialized arrays
238
-     *
239
-     * @param string $field_name
240
-     * @param mixed  $field_value_from_db
241
-     * @throws InvalidArgumentException
242
-     * @throws InvalidInterfaceException
243
-     * @throws InvalidDataTypeException
244
-     * @throws EE_Error
245
-     */
246
-    public function set_from_db($field_name, $field_value_from_db)
247
-    {
248
-        $field_obj = $this->_model->field_settings_for($field_name);
249
-        if ($field_obj instanceof EE_Model_Field_Base) {
250
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
251
-            // eg, a CPT model object could have an entry in the posts table, but no
252
-            // entry in the meta table. Meaning that all its columns in the meta table
253
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
254
-            if ($field_value_from_db === null) {
255
-                if ($field_obj->is_nullable()) {
256
-                    // if the field allows nulls, then let it be null
257
-                    $field_value = null;
258
-                } else {
259
-                    $field_value = $field_obj->get_default_value();
260
-                }
261
-            } else {
262
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
263
-            }
264
-            $this->_fields[ $field_name ] = $field_value;
265
-            $this->_clear_cached_property($field_name);
266
-        }
267
-    }
268
-
269
-
270
-    /**
271
-     * Overrides parent because parent expects old models.
272
-     * This also doesn't do any validation, and won't work for serialized arrays
273
-     *
274
-     * @param string $field_name
275
-     * @param mixed  $field_value
276
-     * @param bool   $use_default
277
-     * @throws InvalidArgumentException
278
-     * @throws InvalidInterfaceException
279
-     * @throws InvalidDataTypeException
280
-     * @throws EE_Error
281
-     * @throws ReflectionException
282
-     * @throws ReflectionException
283
-     * @throws ReflectionException
284
-     */
285
-    public function set($field_name, $field_value, $use_default = false)
286
-    {
287
-        // if not using default and nothing has changed, and object has already been setup (has ID),
288
-        // then don't do anything
289
-        if (
290
-            ! $use_default
291
-            && $this->_fields[ $field_name ] === $field_value
292
-            && $this->ID()
293
-        ) {
294
-            return;
295
-        }
296
-        $this->_has_changes = true;
297
-        $field_obj          = $this->_model->field_settings_for($field_name);
298
-        if ($field_obj instanceof EE_Model_Field_Base) {
299
-            // if ( method_exists( $field_obj, 'set_timezone' )) {
300
-            if ($field_obj instanceof EE_Datetime_Field) {
301
-                $field_obj->set_timezone($this->_timezone);
302
-                $field_obj->set_date_format($this->_dt_frmt);
303
-                $field_obj->set_time_format($this->_tm_frmt);
304
-            }
305
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
306
-            // should the value be null?
307
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
308
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
309
-                /**
310
-                 * To save having to refactor all the models, if a default value is used for a
311
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
312
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
313
-                 * object.
314
-                 *
315
-                 * @since 4.6.10+
316
-                 */
317
-                if (
318
-                    $field_obj instanceof EE_Datetime_Field
319
-                    && $this->_fields[ $field_name ] !== null
320
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
321
-                ) {
322
-                    empty($this->_fields[ $field_name ])
323
-                        ? $this->set($field_name, time())
324
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
325
-                }
326
-            } else {
327
-                $this->_fields[ $field_name ] = $holder_of_value;
328
-            }
329
-            // if we're not in the constructor...
330
-            // now check if what we set was a primary key
331
-            if (
332
-                // note: props_n_values_provided_in_constructor is only set at the END of the constructor
333
-                $this->_props_n_values_provided_in_constructor
334
-                && $field_value
335
-                && $field_name === $this->_model->primary_key_name()
336
-            ) {
337
-                // if so, we want all this object's fields to be filled either with
338
-                // what we've explicitly set on this model
339
-                // or what we have in the db
340
-                // echo "setting primary key!";
341
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
342
-                $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
343
-                foreach ($fields_on_model as $field_obj) {
344
-                    if (
345
-                        ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
346
-                        && $field_obj->get_name() !== $field_name
347
-                    ) {
348
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
349
-                    }
350
-                }
351
-                // oh this model object has an ID? well make sure its in the entity mapper
352
-                $this->_model->add_to_entity_map($this);
353
-            }
354
-            // let's unset any cache for this field_name from the $_cached_properties property.
355
-            $this->_clear_cached_property($field_name);
356
-        } else {
357
-            throw new EE_Error(
358
-                sprintf(
359
-                    esc_html__(
360
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
361
-                        'event_espresso'
362
-                    ),
363
-                    $field_name
364
-                )
365
-            );
366
-        }
367
-    }
368
-
369
-
370
-    /**
371
-     * Gets the value of the primary key.
372
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
373
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
374
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
375
-     *
376
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
377
-     * @throws InvalidArgumentException
378
-     * @throws InvalidInterfaceException
379
-     * @throws InvalidDataTypeException
380
-     * @throws EE_Error
381
-     */
382
-    public function ID()
383
-    {
384
-        // now that we know the name of the variable, use a variable variable to get its value and return its
385
-        if ($this->_model->has_primary_key_field()) {
386
-            return $this->_fields[ $this->_model->primary_key_name() ];
387
-        }
388
-        return $this->_model->get_index_primary_key_string($this->_fields);
389
-    }
390
-
391
-
392
-    /**
393
-     * If a model name is provided (eg Registration), gets the model classname for that model.
394
-     * Also works if a model class's classname is provided (eg EE_Registration).
395
-     *
396
-     * @param null $model_name
397
-     * @return string like EEM_Attendee
398
-     */
399
-    private static function _get_model_classname($model_name = null)
400
-    {
401
-        if (strpos($model_name, 'EE_') === 0) {
402
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
403
-        } else {
404
-            $model_classname = 'EEM_' . $model_name;
405
-        }
406
-        return $model_classname;
407
-    }
408
-
409
-
410
-    /**
411
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
412
-     *
413
-     * @param string $model_classname
414
-     * @param null   $timezone
415
-     * @return EEM_Base
416
-     * @throws ReflectionException
417
-     * @throws InvalidArgumentException
418
-     * @throws InvalidInterfaceException
419
-     * @throws InvalidDataTypeException
420
-     * @throws EE_Error
421
-     */
422
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
423
-    {
424
-        $model_classname = str_replace('EEM_', '', $model_classname);
425
-        $model           = EE_Registry::instance()->load_model($model_classname);
426
-        $model->set_timezone($timezone);
427
-        return $model;
428
-    }
429
-
430
-
431
-    /**
432
-     * This just clears out ONE property if it exists in the cache
433
-     *
434
-     * @param string $property_name the property to remove if it exists (from the _cached_properties array)
435
-     * @return void
436
-     */
437
-    protected function _clear_cached_property($property_name)
438
-    {
439
-        if (isset($this->_cached_properties[ $property_name ])) {
440
-            unset($this->_cached_properties[ $property_name ]);
441
-        }
442
-    }
443
-
444
-
445
-    /**
446
-     * Gets the EEM_*_Model for this class
447
-     *
448
-     * @access public now, as this is more convenient
449
-     * @param      $classname
450
-     * @param null $timezone
451
-     * @return EEM_Base
452
-     * @throws InvalidArgumentException
453
-     * @throws InvalidInterfaceException
454
-     * @throws InvalidDataTypeException
455
-     * @throws EE_Error
456
-     * @throws ReflectionException
457
-     */
458
-    protected static function _get_model($classname, $timezone = null)
459
-    {
460
-        // find model for this class
461
-        if (! $classname) {
462
-            throw new EE_Error(
463
-                sprintf(
464
-                    esc_html__(
465
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
466
-                        'event_espresso'
467
-                    ),
468
-                    $classname
469
-                )
470
-            );
471
-        }
472
-        $modelName = self::_get_model_classname($classname);
473
-        return self::_get_model_instance_with_name($modelName, $timezone);
474
-    }
475
-
476
-
477
-    /**
478
-     * verifies that the specified field is of the correct type
479
-     *
480
-     * @param string $field_name
481
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
482
-     *                                (in cases where the same property may be used for different outputs
483
-     *                                - i.e. datetime, money etc.)
484
-     * @return mixed
485
-     * @throws InvalidArgumentException
486
-     * @throws InvalidInterfaceException
487
-     * @throws InvalidDataTypeException
488
-     * @throws EE_Error
489
-     */
490
-    public function get($field_name, $extra_cache_ref = null)
491
-    {
492
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
493
-    }
494
-
495
-
496
-    /**
497
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
498
-     * This also SETS the cache if we return the actual property!
499
-     *
500
-     * @param string $field_name       the name of the property we're trying to retrieve
501
-     * @param bool   $pretty
502
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
503
-     *                                 (in cases where the same property may be used for different outputs
504
-     *                                 - i.e. datetime, money etc.)
505
-     *                                 It can also accept certain pre-defined "schema" strings
506
-     *                                 to define how to output the property.
507
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
508
-     * @return mixed                   whatever the value for the property is we're retrieving
509
-     * @throws InvalidArgumentException
510
-     * @throws InvalidInterfaceException
511
-     * @throws InvalidDataTypeException
512
-     * @throws EE_Error
513
-     */
514
-    protected function _get_cached_property($field_name, $pretty = false, $extra_cache_ref = null)
515
-    {
516
-        // verify the field exists
517
-        $this->_model->field_settings_for($field_name);
518
-        $cache_type = $pretty ? 'pretty' : 'standard';
519
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
520
-        if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
521
-            return $this->_cached_properties[ $field_name ][ $cache_type ];
522
-        }
523
-        $value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
524
-        $this->_set_cached_property($field_name, $value, $cache_type);
525
-        return $value;
526
-    }
527
-
528
-
529
-    /**
530
-     * If the cache didn't fetch the needed item, this fetches it.
531
-     *
532
-     * @param string $field_name
533
-     * @param bool   $pretty
534
-     * @param string $extra_cache_ref
535
-     * @return mixed
536
-     * @throws InvalidArgumentException
537
-     * @throws InvalidInterfaceException
538
-     * @throws InvalidDataTypeException
539
-     * @throws EE_Error
540
-     */
541
-    protected function _get_fresh_property($field_name, $pretty = false, $extra_cache_ref = null)
542
-    {
543
-        $field_obj = $this->_model->field_settings_for($field_name);
544
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
545
-        if ($field_obj instanceof EE_Datetime_Field) {
546
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
547
-        }
548
-        if (! isset($this->_fields[ $field_name ])) {
549
-            $this->_fields[ $field_name ] = null;
550
-        }
551
-        return $pretty
552
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
553
-            : $field_obj->prepare_for_get($this->_fields[ $field_name ]);
554
-    }
555
-
556
-
557
-    /**
558
-     * For adding an item to the cached_properties property.
559
-     *
560
-     * @access protected
561
-     * @param string      $field_name the property item the corresponding value is for.
562
-     * @param mixed       $value      The value we are caching.
563
-     * @param string|null $cache_type
564
-     * @return void
565
-     * @throws InvalidArgumentException
566
-     * @throws InvalidInterfaceException
567
-     * @throws InvalidDataTypeException
568
-     * @throws EE_Error
569
-     */
570
-    protected function _set_cached_property($field_name, $value, $cache_type = null)
571
-    {
572
-        // first make sure this property exists
573
-        $this->_model->field_settings_for($field_name);
574
-        $cache_type                                             = empty($cache_type) ? 'standard' : $cache_type;
575
-        $this->_cached_properties[ $field_name ][ $cache_type ] = $value;
576
-    }
577
-
578
-
579
-    /**
580
-     * set timezone, formats, and output for EE_Datetime_Field objects
581
-     *
582
-     * @param EE_Datetime_Field $datetime_field
583
-     * @param bool              $pretty
584
-     * @param null              $date_or_time
585
-     * @return void
586
-     * @throws InvalidArgumentException
587
-     * @throws InvalidInterfaceException
588
-     * @throws InvalidDataTypeException
589
-     */
590
-    protected function _prepare_datetime_field(
591
-        EE_Datetime_Field $datetime_field,
592
-        $pretty = false,
593
-        $date_or_time = null
594
-    ) {
595
-        $datetime_field->set_timezone($this->_timezone);
596
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
597
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
598
-        // set the output returned
599
-        switch ($date_or_time) {
600
-            case 'D':
601
-                $datetime_field->set_date_time_output('date');
602
-                break;
603
-            case 'T':
604
-                $datetime_field->set_date_time_output('time');
605
-                break;
606
-            default:
607
-                $datetime_field->set_date_time_output();
608
-        }
609
-    }
610
-
611
-
612
-    /**
613
-     * @param $props_n_values
614
-     * @param $classname
615
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
616
-     * @throws ReflectionException
617
-     * @throws InvalidArgumentException
618
-     * @throws InvalidInterfaceException
619
-     * @throws InvalidDataTypeException
620
-     * @throws EE_Error
621
-     */
622
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
623
-    {
624
-        // TODO: will not work for Term_Relationships because they have no PK!
625
-        $primary_id_ref = self::_get_primary_key_name($classname);
626
-        if (
627
-            array_key_exists($primary_id_ref, $props_n_values)
628
-            && ! empty($props_n_values[ $primary_id_ref ])
629
-        ) {
630
-            $id = $props_n_values[ $primary_id_ref ];
631
-            return self::_get_model($classname)->get_from_entity_map($id);
632
-        }
633
-        return false;
634
-    }
635
-
636
-
637
-    /**
638
-     * returns the name of the primary key attribute
639
-     *
640
-     * @param null $classname
641
-     * @return string
642
-     * @throws InvalidArgumentException
643
-     * @throws InvalidInterfaceException
644
-     * @throws InvalidDataTypeException
645
-     * @throws EE_Error
646
-     * @throws ReflectionException
647
-     */
648
-    protected static function _get_primary_key_name($classname = null)
649
-    {
650
-        if (! $classname) {
651
-            throw new EE_Error(
652
-                sprintf(
653
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
654
-                    $classname
655
-                )
656
-            );
657
-        }
658
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
659
-    }
660
-
661
-
662
-    /**
663
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
664
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
665
-     * 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
666
-     * we return false.
667
-     *
668
-     * @param array  $props_n_values    incoming array of properties and their values
669
-     * @param string $classname         the classname of the child class
670
-     * @param null   $timezone
671
-     * @param array  $date_formats      incoming date_formats in an array where the first value is the
672
-     *                                  date_format and the second value is the time format
673
-     * @return mixed (EE_Base_Class|bool)
674
-     * @throws InvalidArgumentException
675
-     * @throws InvalidInterfaceException
676
-     * @throws InvalidDataTypeException
677
-     * @throws EE_Error
678
-     * @throws ReflectionException
679
-     * @throws ReflectionException
680
-     * @throws ReflectionException
681
-     */
682
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = [])
683
-    {
684
-        $existing = null;
685
-        $model    = self::_get_model($classname, $timezone);
686
-        if ($model->has_primary_key_field()) {
687
-            $primary_id_ref = self::_get_primary_key_name($classname);
688
-            if (
689
-                array_key_exists($primary_id_ref, $props_n_values)
690
-                && ! empty($props_n_values[ $primary_id_ref ])
691
-            ) {
692
-                $existing = $model->get_one_by_ID(
693
-                    $props_n_values[ $primary_id_ref ]
694
-                );
695
-            }
696
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
697
-            // no primary key on this model, but there's still a matching item in the DB
698
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
699
-                self::_get_model($classname, $timezone)
700
-                    ->get_index_primary_key_string($props_n_values)
701
-            );
702
-        }
703
-        if ($existing) {
704
-            // set date formats if present before setting values
705
-            if (! empty($date_formats) && is_array($date_formats)) {
706
-                $existing->set_date_format($date_formats[0]);
707
-                $existing->set_time_format($date_formats[1]);
708
-            } else {
709
-                // set default formats for date and time
710
-                $existing->set_date_format(get_option('date_format'));
711
-                $existing->set_time_format(get_option('time_format'));
712
-            }
713
-            foreach ($props_n_values as $property => $field_value) {
714
-                $existing->set($property, $field_value);
715
-            }
716
-            return $existing;
717
-        }
718
-        return false;
719
-    }
720
-
721
-
722
-    /**
723
-     * This sets the internal date format to what is sent in to be used as the new default for the class
724
-     * internally instead of wp set date format options
725
-     *
726
-     * @param string $format should be a format recognizable by PHP date() functions.
727
-     * @since 4.6
728
-     */
729
-    public function set_date_format($format)
730
-    {
731
-        $this->_dt_frmt = $format;
732
-        // clear cached_properties because they won't be relevant now.
733
-        $this->_clear_cached_properties();
734
-    }
735
-
736
-
737
-    /**
738
-     * This sets the internal time format string to what is sent in to be used as the new default for the
739
-     * class internally instead of wp set time format options.
740
-     *
741
-     * @param string $format should be a format recognizable by PHP date() functions.
742
-     * @since 4.6
743
-     */
744
-    public function set_time_format($format)
745
-    {
746
-        $this->_tm_frmt = $format;
747
-        // clear cached_properties because they won't be relevant now.
748
-        $this->_clear_cached_properties();
749
-    }
750
-
751
-
752
-    /**
753
-     * This just takes care of clearing out the cached_properties
754
-     *
755
-     * @return void
756
-     */
757
-    protected function _clear_cached_properties()
758
-    {
759
-        $this->_cached_properties = [];
760
-    }
761
-
762
-
763
-    /**
764
-     * Sets whether or not this model object should be allowed to be saved to the DB.
765
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
766
-     * you got new information that somehow made you change your mind.
767
-     *
768
-     * @param boolean $allow_persist
769
-     * @return boolean
770
-     */
771
-    public function set_allow_persist($allow_persist)
772
-    {
773
-        return $this->_allow_persist = $allow_persist;
774
-    }
775
-
776
-
777
-    /**
778
-     * Gets the field's original value when this object was constructed during this request.
779
-     * This can be helpful when determining if a model object has changed or not
780
-     *
781
-     * @param string $field_name
782
-     * @return mixed|null
783
-     * @throws InvalidArgumentException
784
-     * @throws InvalidInterfaceException
785
-     * @throws InvalidDataTypeException
786
-     * @throws EE_Error
787
-     */
788
-    public function get_original($field_name)
789
-    {
790
-        if (
791
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
792
-            && $field_settings = $this->_model->field_settings_for($field_name)
793
-        ) {
794
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
795
-        }
796
-        return null;
797
-    }
798
-
799
-
800
-    /**
801
-     * @param EE_Base_Class $obj
802
-     * @return string
803
-     */
804
-    public function get_class($obj)
805
-    {
806
-        return get_class($obj);
807
-    }
808
-
809
-
810
-    /**
811
-     * Set custom select values for model.
812
-     *
813
-     * @param array $custom_select_values
814
-     */
815
-    public function setCustomSelectsValues(array $custom_select_values)
816
-    {
817
-        $this->custom_selection_results = $custom_select_values;
818
-    }
819
-
820
-
821
-    /**
822
-     * Returns the custom select value for the provided alias if its set.
823
-     * If not set, returns null.
824
-     *
825
-     * @param string $alias
826
-     * @return string|int|float|null
827
-     */
828
-    public function getCustomSelect($alias)
829
-    {
830
-        return isset($this->custom_selection_results[ $alias ])
831
-            ? $this->custom_selection_results[ $alias ]
832
-            : null;
833
-    }
834
-
835
-
836
-    /**
837
-     * This sets the field value on the db column if it exists for the given $column_name or
838
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
839
-     *
840
-     * @param string $field_name  Must be the exact column name.
841
-     * @param mixed  $field_value The value to set.
842
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
843
-     * @throws InvalidArgumentException
844
-     * @throws InvalidInterfaceException
845
-     * @throws InvalidDataTypeException
846
-     * @throws EE_Error
847
-     * @throws ReflectionException
848
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
849
-     */
850
-    public function set_field_or_extra_meta($field_name, $field_value)
851
-    {
852
-        if ($this->_model->has_field($field_name)) {
853
-            $this->set($field_name, $field_value);
854
-            return true;
855
-        }
856
-        // ensure this object is saved first so that extra meta can be properly related.
857
-        $this->save();
858
-        return $this->update_extra_meta($field_name, $field_value);
859
-    }
860
-
861
-
862
-    /**
863
-     *        Saves this object to the database. An array may be supplied to set some values on this
864
-     * object just before saving.
865
-     *
866
-     * @access public
867
-     * @param array $set_cols_n_values keys are field names, values are their new values,
868
-     *                                 if provided during the save() method (often client code will change the fields'
869
-     *                                 values before calling save)
870
-     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
871
-     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
872
-     * @throws InvalidInterfaceException
873
-     * @throws InvalidDataTypeException
874
-     * @throws EE_Error
875
-     * @throws InvalidArgumentException
876
-     * @throws ReflectionException
877
-     * @throws ReflectionException
878
-     * @throws ReflectionException
879
-     */
880
-    public function save($set_cols_n_values = [])
881
-    {
882
-        /**
883
-         * Filters the fields we're about to save on the model object
884
-         *
885
-         * @param array         $set_cols_n_values
886
-         * @param EE_Base_Class $model_object
887
-         */
888
-        $set_cols_n_values = (array) apply_filters(
889
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
890
-            $set_cols_n_values,
891
-            $this
892
-        );
893
-        // set attributes as provided in $set_cols_n_values
894
-        foreach ($set_cols_n_values as $column => $value) {
895
-            $this->set($column, $value);
896
-        }
897
-        // no changes ? then don't do anything
898
-        if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
899
-            return 0;
900
-        }
901
-        /**
902
-         * Saving a model object.
903
-         * Before we perform a save, this action is fired.
904
-         *
905
-         * @param EE_Base_Class $model_object the model object about to be saved.
906
-         */
907
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
908
-        if (! $this->allow_persist()) {
909
-            return 0;
910
-        }
911
-        // now get current attribute values
912
-        $save_cols_n_values = $this->_fields;
913
-        // if the object already has an ID, update it. Otherwise, insert it
914
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
915
-        // They have been
916
-        $old_assumption_concerning_value_preparation = $this->_model
917
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
918
-        $this->_model->assume_values_already_prepared_by_model_object(true);
919
-        // does this model have an autoincrement PK?
920
-        if ($this->_model->has_primary_key_field()) {
921
-            if ($this->_model->get_primary_key_field()->is_auto_increment()) {
922
-                // ok check if it's set, if so: update; if not, insert
923
-                if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
924
-                    $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
925
-                } else {
926
-                    unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
927
-                    $results = $this->_model->insert($save_cols_n_values);
928
-                    if ($results) {
929
-                        // if successful, set the primary key
930
-                        // but don't use the normal SET method, because it will check if
931
-                        // an item with the same ID exists in the mapper & db, then
932
-                        // will find it in the db (because we just added it) and THAT object
933
-                        // will get added to the mapper before we can add this one!
934
-                        // but if we just avoid using the SET method, all that headache can be avoided
935
-                        $pk_field_name                   = $this->_model->primary_key_name();
936
-                        $this->_fields[ $pk_field_name ] = $results;
937
-                        $this->_clear_cached_property($pk_field_name);
938
-                        $this->_model->add_to_entity_map($this);
939
-                        $this->_update_cached_related_model_objs_fks();
940
-                    }
941
-                }
942
-            } else {// PK is NOT auto-increment
943
-                // so check if one like it already exists in the db
944
-                if ($this->_model->exists_by_ID($this->ID())) {
945
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
946
-                        throw new EE_Error(
947
-                            sprintf(
948
-                                esc_html__(
949
-                                    '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',
950
-                                    'event_espresso'
951
-                                ),
952
-                                get_class($this),
953
-                                get_class($this->_model) . '::instance()->add_to_entity_map()',
954
-                                get_class($this->_model) . '::instance()->get_one_by_ID()',
955
-                                '<br />'
956
-                            )
957
-                        );
958
-                    }
959
-                    $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
960
-                } else {
961
-                    $results = $this->_model->insert($save_cols_n_values);
962
-                    $this->_update_cached_related_model_objs_fks();
963
-                }
964
-            }
965
-        } else {// there is NO primary key
966
-            $already_in_db = false;
967
-            foreach ($this->_model->unique_indexes() as $index) {
968
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
969
-                if ($this->_model->exists([$uniqueness_where_params])) {
970
-                    $already_in_db = true;
971
-                }
972
-            }
973
-            if ($already_in_db) {
974
-                $combined_pk_fields_n_values = array_intersect_key(
975
-                    $save_cols_n_values,
976
-                    $this->_model->get_combined_primary_key_fields()
977
-                );
978
-                $results                     = $this->_model->update(
979
-                    $save_cols_n_values,
980
-                    $combined_pk_fields_n_values
981
-                );
982
-            } else {
983
-                $results = $this->_model->insert($save_cols_n_values);
984
-            }
985
-        }
986
-        // restore the old assumption about values being prepared by the model object
987
-        $this->_model->assume_values_already_prepared_by_model_object(
988
-            $old_assumption_concerning_value_preparation
989
-        );
990
-        /**
991
-         * After saving the model object this action is called
992
-         *
993
-         * @param EE_Base_Class $model_object which was just saved
994
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
995
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
996
-         */
997
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
998
-        $this->_has_changes = false;
999
-        return $results;
1000
-    }
1001
-
1002
-
1003
-    /**
1004
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
1005
-     * A $previous_value can be specified in case there are many meta rows with the same key
1006
-     *
1007
-     * @param string $meta_key
1008
-     * @param mixed  $meta_value
1009
-     * @param mixed  $previous_value
1010
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
1011
-     *                  NOTE: if the values haven't changed, returns 0
1012
-     * @throws InvalidArgumentException
1013
-     * @throws InvalidInterfaceException
1014
-     * @throws InvalidDataTypeException
1015
-     * @throws EE_Error
1016
-     * @throws ReflectionException
1017
-     */
1018
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
1019
-    {
1020
-        $query_params = [
1021
-            [
1022
-                'EXM_key'  => $meta_key,
1023
-                'OBJ_ID'   => $this->ID(),
1024
-                'EXM_type' => $this->_model->get_this_model_name(),
1025
-            ],
1026
-        ];
1027
-        if ($previous_value !== null) {
1028
-            $query_params[0]['EXM_value'] = $meta_value;
1029
-        }
1030
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
1031
-        if (! $existing_rows_like_that) {
1032
-            return $this->add_extra_meta($meta_key, $meta_value);
1033
-        }
1034
-        foreach ($existing_rows_like_that as $existing_row) {
1035
-            $existing_row->save(['EXM_value' => $meta_value]);
1036
-        }
1037
-        return count($existing_rows_like_that);
1038
-    }
1039
-
1040
-
1041
-    /**
1042
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
1043
-     *
1044
-     * @return boolean
1045
-     */
1046
-    public function allow_persist()
1047
-    {
1048
-        return $this->_allow_persist;
1049
-    }
1050
-
1051
-
1052
-    /**
1053
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1054
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1055
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1056
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1057
-     * 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
1058
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1059
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1060
-     *
1061
-     * @return void
1062
-     * @throws ReflectionException
1063
-     * @throws InvalidArgumentException
1064
-     * @throws InvalidInterfaceException
1065
-     * @throws InvalidDataTypeException
1066
-     * @throws EE_Error
1067
-     */
1068
-    protected function _update_cached_related_model_objs_fks()
1069
-    {
1070
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1071
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1072
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1073
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1074
-                        $this->_model->get_this_model_name()
1075
-                    );
1076
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1077
-                    if ($related_model_obj_in_cache->ID()) {
1078
-                        $related_model_obj_in_cache->save();
1079
-                    }
1080
-                }
1081
-            }
1082
-        }
1083
-    }
1084
-
1085
-
1086
-    /**
1087
-     * in_entity_map
1088
-     * Checks if this model object has been proven to already be in the entity map
1089
-     *
1090
-     * @return boolean
1091
-     * @throws InvalidArgumentException
1092
-     * @throws InvalidInterfaceException
1093
-     * @throws InvalidDataTypeException
1094
-     * @throws EE_Error
1095
-     */
1096
-    public function in_entity_map()
1097
-    {
1098
-        // well, if we looked, did we find it in the entity map?
1099
-        return $this->ID() && $this->_model->get_from_entity_map($this->ID()) === $this;
1100
-    }
1101
-
1102
-
1103
-    /**
1104
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
1105
-     * no other extra meta for this model object have the same key. Returns TRUE if the
1106
-     * extra meta row was entered, false if not
1107
-     *
1108
-     * @param string  $meta_key
1109
-     * @param mixed   $meta_value
1110
-     * @param boolean $unique
1111
-     * @return boolean
1112
-     * @throws InvalidArgumentException
1113
-     * @throws InvalidInterfaceException
1114
-     * @throws InvalidDataTypeException
1115
-     * @throws EE_Error
1116
-     * @throws ReflectionException
1117
-     * @throws ReflectionException
1118
-     */
1119
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
1120
-    {
1121
-        if ($unique) {
1122
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
1123
-                [
1124
-                    [
1125
-                        'EXM_key'  => $meta_key,
1126
-                        'OBJ_ID'   => $this->ID(),
1127
-                        'EXM_type' => $this->_model->get_this_model_name(),
1128
-                    ],
1129
-                ]
1130
-            );
1131
-            if ($existing_extra_meta) {
1132
-                return false;
1133
-            }
1134
-        }
1135
-        $new_extra_meta = EE_Extra_Meta::new_instance(
1136
-            [
1137
-                'EXM_key'   => $meta_key,
1138
-                'EXM_value' => $meta_value,
1139
-                'OBJ_ID'    => $this->ID(),
1140
-                'EXM_type'  => $this->_model->get_this_model_name(),
1141
-            ]
1142
-        );
1143
-        $new_extra_meta->save();
1144
-        return true;
1145
-    }
1146
-
1147
-
1148
-    /**
1149
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1150
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1151
-     *
1152
-     * @param string $relationName
1153
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
1154
-     * @throws InvalidArgumentException
1155
-     * @throws InvalidInterfaceException
1156
-     * @throws InvalidDataTypeException
1157
-     * @throws EE_Error
1158
-     * @throws ReflectionException
1159
-     */
1160
-    public function get_all_from_cache($relationName)
1161
-    {
1162
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
1163
-        // if the result is not an array, but exists, make it an array
1164
-        $objects = is_array($objects) ? $objects : [$objects];
1165
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
1166
-        // basically, if this model object was stored in the session, and these cached model objects
1167
-        // already have IDs, let's make sure they're in their model's entity mapper
1168
-        // otherwise we will have duplicates next time we call
1169
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
1170
-        $related_model = EE_Registry::instance()->load_model($relationName);
1171
-        foreach ($objects as $model_object) {
1172
-            if ($related_model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
1173
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
1174
-                if ($model_object->ID()) {
1175
-                    $related_model->add_to_entity_map($model_object);
1176
-                }
1177
-            } else {
1178
-                throw new EE_Error(
1179
-                    sprintf(
1180
-                        esc_html__(
1181
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
1182
-                            'event_espresso'
1183
-                        ),
1184
-                        $relationName,
1185
-                        gettype($model_object)
1186
-                    )
1187
-                );
1188
-            }
1189
-        }
1190
-        return $objects;
1191
-    }
1192
-
1193
-
1194
-    /**
1195
-     * This retrieves the value of the db column set on this class or if that's not present
1196
-     * it will attempt to retrieve from extra_meta if found.
1197
-     * Example Usage:
1198
-     * Via EE_Message child class:
1199
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
1200
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
1201
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
1202
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
1203
-     * value for those extra fields dynamically via the EE_message object.
1204
-     *
1205
-     * @param string $field_name expecting the fully qualified field name.
1206
-     * @return mixed|null  value for the field if found.  null if not found.
1207
-     * @throws ReflectionException
1208
-     * @throws InvalidArgumentException
1209
-     * @throws InvalidInterfaceException
1210
-     * @throws InvalidDataTypeException
1211
-     * @throws EE_Error
1212
-     */
1213
-    public function get_field_or_extra_meta($field_name)
1214
-    {
1215
-        if ($this->_model->has_field($field_name)) {
1216
-            $column_value = $this->get($field_name);
1217
-        } else {
1218
-            // This isn't a column in the main table, let's see if it is in the extra meta.
1219
-            $column_value = $this->get_extra_meta($field_name, true);
1220
-        }
1221
-        return $column_value;
1222
-    }
1223
-
1224
-
1225
-    /**
1226
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
1227
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
1228
-     * You can specify $default is case you haven't found the extra meta
1229
-     *
1230
-     * @param string  $meta_key
1231
-     * @param boolean $single
1232
-     * @param mixed   $default if we don't find anything, what should we return?
1233
-     * @return mixed single value if $single; array if ! $single
1234
-     * @throws ReflectionException
1235
-     * @throws InvalidArgumentException
1236
-     * @throws InvalidInterfaceException
1237
-     * @throws InvalidDataTypeException
1238
-     * @throws EE_Error
1239
-     */
1240
-    public function get_extra_meta($meta_key, $single = false, $default = null)
1241
-    {
1242
-        if ($single) {
1243
-            $result = $this->get_first_related(
1244
-                'Extra_Meta',
1245
-                [['EXM_key' => $meta_key]]
1246
-            );
1247
-            if ($result instanceof EE_Extra_Meta) {
1248
-                return $result->value();
1249
-            }
1250
-        } else {
1251
-            $results = $this->get_many_related(
1252
-                'Extra_Meta',
1253
-                [['EXM_key' => $meta_key]]
1254
-            );
1255
-            if ($results) {
1256
-                $values = [];
1257
-                foreach ($results as $result) {
1258
-                    if ($result instanceof EE_Extra_Meta) {
1259
-                        $values[ $result->ID() ] = $result->value();
1260
-                    }
1261
-                }
1262
-                return $values;
1263
-            }
1264
-        }
1265
-        // if nothing discovered yet return default.
1266
-        return apply_filters(
1267
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
1268
-            $default,
1269
-            $meta_key,
1270
-            $single,
1271
-            $this
1272
-        );
1273
-    }
1274
-
1275
-
1276
-    /**
1277
-     * Gets the first (ie, one) related model object of the specified type.
1278
-     *
1279
-     * @param string $relationName key in the model's _model_relations array
1280
-     * @param array  $query_params @see
1281
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1282
-     * @return EE_Base_Class (not an array, a single object)
1283
-     * @throws ReflectionException
1284
-     * @throws InvalidArgumentException
1285
-     * @throws InvalidInterfaceException
1286
-     * @throws InvalidDataTypeException
1287
-     * @throws EE_Error
1288
-     */
1289
-    public function get_first_related($relationName, $query_params = [])
1290
-    {
1291
-        $model_relation = $this->_model->related_settings_for($relationName);
1292
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
1293
-            // if they've provided some query parameters, don't bother trying to cache the result
1294
-            // also make sure we're not caching the result of get_first_related
1295
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
1296
-            if (
1297
-                $query_params
1298
-                || ! $model_relation instanceof EE_Belongs_To_Relation
1299
-            ) {
1300
-                $related_model_object = $this->_model->get_first_related(
1301
-                    $this,
1302
-                    $relationName,
1303
-                    $query_params
1304
-                );
1305
-            } else {
1306
-                // first, check if we've already cached the result of this query
1307
-                $cached_result = $this->get_one_from_cache($relationName);
1308
-                if (! $cached_result) {
1309
-                    $related_model_object = $this->_model->get_first_related(
1310
-                        $this,
1311
-                        $relationName,
1312
-                        $query_params
1313
-                    );
1314
-                    $this->cache($relationName, $related_model_object);
1315
-                } else {
1316
-                    $related_model_object = $cached_result;
1317
-                }
1318
-            }
1319
-        } else {
1320
-            $related_model_object = null;
1321
-            // this doesn't exist in the Db,
1322
-            // but maybe the relation is of type belongs to, and so the related thing might
1323
-            if ($model_relation instanceof EE_Belongs_To_Relation) {
1324
-                $related_model_object = $this->_model->get_first_related(
1325
-                    $this,
1326
-                    $relationName,
1327
-                    $query_params
1328
-                );
1329
-            }
1330
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
1331
-            // just get what's cached on this object
1332
-            if (! $related_model_object) {
1333
-                $related_model_object = $this->get_one_from_cache($relationName);
1334
-            }
1335
-        }
1336
-        return $related_model_object;
1337
-    }
1338
-
1339
-
1340
-    /**
1341
-     * Gets all the related model objects of the specified type. Eg, if the current class if
1342
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
1343
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
1344
-     * because we want to get even deleted items etc.
1345
-     *
1346
-     * @param string $relationName key in the model's _model_relations array
1347
-     * @param array  $query_params @see
1348
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1349
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
1350
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
1351
-     *                             results if you want IDs
1352
-     * @throws ReflectionException
1353
-     * @throws InvalidArgumentException
1354
-     * @throws InvalidInterfaceException
1355
-     * @throws InvalidDataTypeException
1356
-     * @throws EE_Error
1357
-     */
1358
-    public function get_many_related($relationName, $query_params = [])
1359
-    {
1360
-        if ($this->ID()) {
1361
-            // this exists in the DB, so get the related things from either the cache or the DB
1362
-            // if there are query parameters, forget about caching the related model objects.
1363
-            if ($query_params) {
1364
-                $related_model_objects = $this->_model->get_all_related(
1365
-                    $this,
1366
-                    $relationName,
1367
-                    $query_params
1368
-                );
1369
-            } else {
1370
-                // did we already cache the result of this query?
1371
-                $cached_results = $this->get_all_from_cache($relationName);
1372
-                if (! $cached_results) {
1373
-                    $related_model_objects = $this->_model->get_all_related(
1374
-                        $this,
1375
-                        $relationName,
1376
-                        $query_params
1377
-                    );
1378
-                    // if no query parameters were passed, then we got all the related model objects
1379
-                    // for that relation. We can cache them then.
1380
-                    foreach ($related_model_objects as $related_model_object) {
1381
-                        $this->cache($relationName, $related_model_object);
1382
-                    }
1383
-                } else {
1384
-                    $related_model_objects = $cached_results;
1385
-                }
1386
-            }
1387
-        } else {
1388
-            // this doesn't exist in the DB, so just get the related things from the cache
1389
-            $related_model_objects = $this->get_all_from_cache($relationName);
1390
-        }
1391
-        return $related_model_objects;
1392
-    }
1393
-
1394
-
1395
-    /**
1396
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1397
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1398
-     *
1399
-     * @param string $relationName
1400
-     * @return EE_Base_Class
1401
-     */
1402
-    public function get_one_from_cache($relationName)
1403
-    {
1404
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
1405
-            ? $this->_model_relations[ $relationName ]
1406
-            : null;
1407
-        if (is_array($cached_array_or_object)) {
1408
-            return array_shift($cached_array_or_object);
1409
-        }
1410
-        return $cached_array_or_object;
1411
-    }
1412
-
1413
-
1414
-    /**
1415
-     * cache
1416
-     * stores the passed model object on the current model object.
1417
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
1418
-     *
1419
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
1420
-     *                                       'Registration' associated with this model object
1421
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
1422
-     *                                       that could be a payment or a registration)
1423
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
1424
-     *                                       items which will be stored in an array on this object
1425
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
1426
-     *                                       related thing, no array)
1427
-     * @throws InvalidArgumentException
1428
-     * @throws InvalidInterfaceException
1429
-     * @throws InvalidDataTypeException
1430
-     * @throws EE_Error
1431
-     */
1432
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
1433
-    {
1434
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
1435
-        if (! $object_to_cache instanceof EE_Base_Class) {
1436
-            return false;
1437
-        }
1438
-        // also get "how" the object is related, or throw an error
1439
-        if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1440
-            throw new EE_Error(
1441
-                sprintf(
1442
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
1443
-                    $relationName,
1444
-                    get_class($this)
1445
-                )
1446
-            );
1447
-        }
1448
-        // how many things are related ?
1449
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1450
-            // if it's a "belongs to" relationship, then there's only one related model object
1451
-            // eg, if this is a registration, there's only 1 attendee for it
1452
-            // so for these model objects just set it to be cached
1453
-            $this->_model_relations[ $relationName ] = $object_to_cache;
1454
-            $return                                  = true;
1455
-        } else {
1456
-            // otherwise, this is the "many" side of a one to many relationship,
1457
-            // so we'll add the object to the array of related objects for that type.
1458
-            // eg: if this is an event, there are many registrations for that event,
1459
-            // so we cache the registrations in an array
1460
-            if (! is_array($this->_model_relations[ $relationName ])) {
1461
-                // if for some reason, the cached item is a model object,
1462
-                // then stick that in the array, otherwise start with an empty array
1463
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
1464
-                                                           instanceof
1465
-                                                           EE_Base_Class
1466
-                    ? [$this->_model_relations[ $relationName ]] : [];
1467
-            }
1468
-            // first check for a cache_id which is normally empty
1469
-            if (! empty($cache_id)) {
1470
-                // if the cache_id exists, then it means we are purposely trying to cache this
1471
-                // with a known key that can then be used to retrieve the object later on
1472
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
1473
-                $return                                               = $cache_id;
1474
-            } elseif ($object_to_cache->ID()) {
1475
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
1476
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
1477
-                $return                                                            = $object_to_cache->ID();
1478
-            } else {
1479
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
1480
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
1481
-                // move the internal pointer to the end of the array
1482
-                end($this->_model_relations[ $relationName ]);
1483
-                // and grab the key so that we can return it
1484
-                $return = key($this->_model_relations[ $relationName ]);
1485
-            }
1486
-        }
1487
-        return $return;
1488
-    }
1489
-
1490
-
1491
-    /**
1492
-     * This just returns whatever is set for the current timezone.
1493
-     *
1494
-     * @access public
1495
-     * @return string timezone string
1496
-     */
1497
-    public function get_timezone()
1498
-    {
1499
-        return $this->_timezone;
1500
-    }
1501
-
1502
-
1503
-    /**
1504
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
1505
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
1506
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
1507
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
1508
-     *
1509
-     * @access public
1510
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
1511
-     * @return void
1512
-     * @throws InvalidArgumentException
1513
-     * @throws InvalidInterfaceException
1514
-     * @throws InvalidDataTypeException
1515
-     */
1516
-    public function set_timezone($timezone = '')
1517
-    {
1518
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
1519
-        // make sure we clear all cached properties because they won't be relevant now
1520
-        $this->_clear_cached_properties();
1521
-        // make sure we update field settings and the date for all EE_Datetime_Fields
1522
-        $model_fields = $this->_model->field_settings();
1523
-        foreach ($model_fields as $field_name => $field_obj) {
1524
-            if ($field_obj instanceof EE_Datetime_Field) {
1525
-                $field_obj->set_timezone($this->_timezone);
1526
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
1527
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
1528
-                }
1529
-            }
1530
-        }
1531
-    }
1532
-
1533
-
1534
-    /**
1535
-     * This returns the current internal set format for the date and time formats.
1536
-     *
1537
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
1538
-     *                             where the first value is the date format and the second value is the time format.
1539
-     * @return mixed string|array
1540
-     */
1541
-    public function get_format($full = true)
1542
-    {
1543
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1544
-    }
1545
-
1546
-
1547
-    /**
1548
-     * update_cache_after_object_save
1549
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
1550
-     * obtained after being saved to the db
1551
-     *
1552
-     * @param string        $relationName       - the type of object that is cached
1553
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
1554
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
1555
-     * @return boolean TRUE on success, FALSE on fail
1556
-     * @throws InvalidArgumentException
1557
-     * @throws InvalidInterfaceException
1558
-     * @throws InvalidDataTypeException
1559
-     * @throws EE_Error
1560
-     */
1561
-    public function update_cache_after_object_save(
1562
-        $relationName,
1563
-        EE_Base_Class $newly_saved_object,
1564
-        $current_cache_id = ''
1565
-    ) {
1566
-        // verify that incoming object is of the correct type
1567
-        $obj_class = 'EE_' . $relationName;
1568
-        if ($newly_saved_object instanceof $obj_class) {
1569
-            /* @type EE_Base_Class $newly_saved_object */
1570
-            // now get the type of relation
1571
-            $relationship_to_model = $this->_model->related_settings_for($relationName);
1572
-            // if this is a 1:1 relationship
1573
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1574
-                // then just replace the cached object with the newly saved object
1575
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
1576
-                return true;
1577
-                // or if it's some kind of sordid feral polyamorous relationship...
1578
-            }
1579
-            if (
1580
-                is_array($this->_model_relations[ $relationName ])
1581
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
1582
-            ) {
1583
-                // then remove the current cached item
1584
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
1585
-                // and cache the newly saved object using it's new ID
1586
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
1587
-                return true;
1588
-            }
1589
-        }
1590
-        return false;
1591
-    }
1592
-
1593
-
1594
-    /**
1595
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1596
-     * matching the given query conditions.
1597
-     *
1598
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1599
-     * @param int   $limit              How many objects to return.
1600
-     * @param array $query_params       Any additional conditions on the query.
1601
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1602
-     *                                  you can indicate just the columns you want returned
1603
-     * @return array|EE_Base_Class[]
1604
-     * @throws ReflectionException
1605
-     * @throws InvalidArgumentException
1606
-     * @throws InvalidInterfaceException
1607
-     * @throws InvalidDataTypeException
1608
-     * @throws EE_Error
1609
-     */
1610
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1611
-    {
1612
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1613
-            ? $this->_model->get_primary_key_field()->get_name()
1614
-            : $field_to_order_by;
1615
-        $current_value = ! empty($field) ? $this->get($field) : null;
1616
-        if (empty($field) || empty($current_value)) {
1617
-            return [];
1618
-        }
1619
-        return $this->_model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1620
-    }
1621
-
1622
-
1623
-    /**
1624
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1625
-     * matching the given query conditions.
1626
-     *
1627
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1628
-     * @param int   $limit              How many objects to return.
1629
-     * @param array $query_params       Any additional conditions on the query.
1630
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1631
-     *                                  you can indicate just the columns you want returned
1632
-     * @return array|EE_Base_Class[]
1633
-     * @throws ReflectionException
1634
-     * @throws InvalidArgumentException
1635
-     * @throws InvalidInterfaceException
1636
-     * @throws InvalidDataTypeException
1637
-     * @throws EE_Error
1638
-     */
1639
-    public function previous_x(
1640
-        $field_to_order_by = null,
1641
-        $limit = 1,
1642
-        $query_params = [],
1643
-        $columns_to_select = null
1644
-    ) {
1645
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1646
-            ? $this->_model->get_primary_key_field()->get_name()
1647
-            : $field_to_order_by;
1648
-        $current_value = ! empty($field) ? $this->get($field) : null;
1649
-        if (empty($field) || empty($current_value)) {
1650
-            return [];
1651
-        }
1652
-        return $this->_model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1653
-    }
1654
-
1655
-
1656
-    /**
1657
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1658
-     * matching the given query conditions.
1659
-     *
1660
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1661
-     * @param array $query_params       Any additional conditions on the query.
1662
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1663
-     *                                  you can indicate just the columns you want returned
1664
-     * @return array|EE_Base_Class
1665
-     * @throws ReflectionException
1666
-     * @throws InvalidArgumentException
1667
-     * @throws InvalidInterfaceException
1668
-     * @throws InvalidDataTypeException
1669
-     * @throws EE_Error
1670
-     */
1671
-    public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1672
-    {
1673
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1674
-            ? $this->_model->get_primary_key_field()->get_name()
1675
-            : $field_to_order_by;
1676
-        $current_value = ! empty($field) ? $this->get($field) : null;
1677
-        if (empty($field) || empty($current_value)) {
1678
-            return [];
1679
-        }
1680
-        return $this->_model->next($current_value, $field, $query_params, $columns_to_select);
1681
-    }
1682
-
1683
-
1684
-    /**
1685
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1686
-     * matching the given query conditions.
1687
-     *
1688
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1689
-     * @param array $query_params       Any additional conditions on the query.
1690
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1691
-     *                                  you can indicate just the column you want returned
1692
-     * @return array|EE_Base_Class
1693
-     * @throws ReflectionException
1694
-     * @throws InvalidArgumentException
1695
-     * @throws InvalidInterfaceException
1696
-     * @throws InvalidDataTypeException
1697
-     * @throws EE_Error
1698
-     */
1699
-    public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1700
-    {
1701
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1702
-            ? $this->_model->get_primary_key_field()->get_name()
1703
-            : $field_to_order_by;
1704
-        $current_value = ! empty($field) ? $this->get($field) : null;
1705
-        if (empty($field) || empty($current_value)) {
1706
-            return [];
1707
-        }
1708
-        return $this->_model->previous($current_value, $field, $query_params, $columns_to_select);
1709
-    }
1710
-
1711
-
1712
-    /**
1713
-     * This is used to return the internal DateTime object used for a field that is a
1714
-     * EE_Datetime_Field.
1715
-     *
1716
-     * @param string $field_name               The field name retrieving the DateTime object.
1717
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1718
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1719
-     *                                         EE_Datetime_Field and but the field value is null, then
1720
-     *                                         just null is returned (because that indicates that likely
1721
-     *                                         this field is nullable).
1722
-     * @throws InvalidArgumentException
1723
-     * @throws InvalidDataTypeException
1724
-     * @throws InvalidInterfaceException
1725
-     */
1726
-    public function get_DateTime_object($field_name)
1727
-    {
1728
-        $field_settings = $this->_model->field_settings_for($field_name);
1729
-        if (! $field_settings instanceof EE_Datetime_Field) {
1730
-            EE_Error::add_error(
1731
-                sprintf(
1732
-                    esc_html__(
1733
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1734
-                        'event_espresso'
1735
-                    ),
1736
-                    $field_name
1737
-                ),
1738
-                __FILE__,
1739
-                __FUNCTION__,
1740
-                __LINE__
1741
-            );
1742
-            return false;
1743
-        }
1744
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1745
-            ? clone $this->_fields[ $field_name ]
1746
-            : null;
1747
-    }
1748
-
1749
-
1750
-
1751
-
1752
-    /**
1753
-     * NOTE ABOUT BELOW:
1754
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1755
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1756
-     * you want to set both date and time at the same time, you can just use the models default set($field_name,$value)
1757
-     * method and make sure you send the entire datetime value for setting.
1758
-     */
1759
-
1760
-
1761
-    /**
1762
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1763
-     * can be easily used as the value of form input.
1764
-     *
1765
-     * @param string $field_name
1766
-     * @return void
1767
-     * @throws InvalidArgumentException
1768
-     * @throws InvalidInterfaceException
1769
-     * @throws InvalidDataTypeException
1770
-     * @throws EE_Error
1771
-     */
1772
-    public function f($field_name)
1773
-    {
1774
-        $this->e($field_name, 'form_input');
1775
-    }
1776
-
1777
-
1778
-    /**
1779
-     * To be used in template to immediately echo out the value, and format it for output.
1780
-     * Eg, should call stripslashes and whatnot before echoing
1781
-     *
1782
-     * @param string $field_name      the name of the field as it appears in the DB
1783
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1784
-     *                                (in cases where the same property may be used for different outputs
1785
-     *                                - i.e. datetime, money etc.)
1786
-     * @return void
1787
-     * @throws InvalidArgumentException
1788
-     * @throws InvalidInterfaceException
1789
-     * @throws InvalidDataTypeException
1790
-     * @throws EE_Error
1791
-     */
1792
-    public function e($field_name, $extra_cache_ref = null)
1793
-    {
1794
-        echo $this->get_pretty($field_name, $extra_cache_ref);
1795
-    }
1796
-
1797
-
1798
-    /**
1799
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1800
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1801
-     * to see what options are available.
1802
-     *
1803
-     * @param string $field_name
1804
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1805
-     *                                (in cases where the same property may be used for different outputs
1806
-     *                                - i.e. datetime, money etc.)
1807
-     * @return mixed
1808
-     * @throws InvalidArgumentException
1809
-     * @throws InvalidInterfaceException
1810
-     * @throws InvalidDataTypeException
1811
-     * @throws EE_Error
1812
-     */
1813
-    public function get_pretty($field_name, $extra_cache_ref = null)
1814
-    {
1815
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1816
-    }
1817
-
1818
-
1819
-    /**
1820
-     * Same as `f()` but just returns the value instead of echoing it
1821
-     *
1822
-     * @param string $field_name
1823
-     * @return string
1824
-     * @throws InvalidArgumentException
1825
-     * @throws InvalidInterfaceException
1826
-     * @throws InvalidDataTypeException
1827
-     * @throws EE_Error
1828
-     */
1829
-    public function get_f($field_name)
1830
-    {
1831
-        return (string) $this->get_pretty($field_name, 'form_input');
1832
-    }
1833
-
1834
-
1835
-    /**
1836
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1837
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1838
-     * other echoes the pretty value for dtt)
1839
-     *
1840
-     * @param string $field_name name of model object datetime field holding the value
1841
-     * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1842
-     * @return string            datetime value formatted
1843
-     * @throws InvalidArgumentException
1844
-     * @throws InvalidInterfaceException
1845
-     * @throws InvalidDataTypeException
1846
-     * @throws EE_Error
1847
-     */
1848
-    public function get_date($field_name, $format = '')
1849
-    {
1850
-        return $this->_get_datetime($field_name, $format, null, 'D');
1851
-    }
1852
-
1853
-
1854
-    /**
1855
-     * This simply returns the datetime for the given field name
1856
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1857
-     * (and the equivalent e_date, e_time, e_datetime).
1858
-     *
1859
-     * @access   protected
1860
-     * @param string  $field_name    Field on the instantiated EE_Base_Class child object
1861
-     * @param string  $date_format   valid datetime format used for date
1862
-     *                               (if '' then we just use the default on the field,
1863
-     *                               if NULL we use the last-used format)
1864
-     * @param string  $time_format   Same as above except this is for time format
1865
-     * @param string  $date_or_time  if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1866
-     * @param boolean $echo          Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1867
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1868
-     *                               if field is not a valid dtt field, or void if echoing
1869
-     * @throws InvalidArgumentException
1870
-     * @throws InvalidInterfaceException
1871
-     * @throws InvalidDataTypeException
1872
-     * @throws EE_Error
1873
-     */
1874
-    protected function _get_datetime(
1875
-        $field_name,
1876
-        $date_format = '',
1877
-        $time_format = '',
1878
-        $date_or_time = '',
1879
-        $echo = false
1880
-    ) {
1881
-        // clear cached property
1882
-        $this->_clear_cached_property($field_name);
1883
-        // reset format properties because they are used in get()
1884
-        $this->_dt_frmt = $date_format !== '' ? $date_format : $this->_dt_frmt;
1885
-        $this->_tm_frmt = $time_format !== '' ? $time_format : $this->_tm_frmt;
1886
-        if ($echo) {
1887
-            $this->e($field_name, $date_or_time);
1888
-            return '';
1889
-        }
1890
-        return $this->get($field_name, $date_or_time);
1891
-    }
1892
-
1893
-
1894
-    /**
1895
-     * @param        $field_name
1896
-     * @param string $format
1897
-     * @throws InvalidArgumentException
1898
-     * @throws InvalidInterfaceException
1899
-     * @throws InvalidDataTypeException
1900
-     * @throws EE_Error
1901
-     */
1902
-    public function e_date($field_name, $format = '')
1903
-    {
1904
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1905
-    }
1906
-
1907
-
1908
-    /**
1909
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1910
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1911
-     * other echoes the pretty value for dtt)
1912
-     *
1913
-     * @param string $field_name name of model object datetime field holding the value
1914
-     * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1915
-     * @return string             datetime value formatted
1916
-     * @throws InvalidArgumentException
1917
-     * @throws InvalidInterfaceException
1918
-     * @throws InvalidDataTypeException
1919
-     * @throws EE_Error
1920
-     */
1921
-    public function get_time($field_name, $format = '')
1922
-    {
1923
-        return $this->_get_datetime($field_name, null, $format, 'T');
1924
-    }
1925
-
1926
-
1927
-    /**
1928
-     * @param        $field_name
1929
-     * @param string $format
1930
-     * @throws InvalidArgumentException
1931
-     * @throws InvalidInterfaceException
1932
-     * @throws InvalidDataTypeException
1933
-     * @throws EE_Error
1934
-     */
1935
-    public function e_time($field_name, $format = '')
1936
-    {
1937
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1938
-    }
1939
-
1940
-
1941
-    /**
1942
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1943
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1944
-     * other echoes the pretty value for dtt)
1945
-     *
1946
-     * @param string $field_name  name of model object datetime field holding the value
1947
-     * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1948
-     * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1949
-     * @return string             datetime value formatted
1950
-     * @throws InvalidArgumentException
1951
-     * @throws InvalidInterfaceException
1952
-     * @throws InvalidDataTypeException
1953
-     * @throws EE_Error
1954
-     */
1955
-    public function get_datetime($field_name, $date_format = '', $time_format = '')
1956
-    {
1957
-        return $this->_get_datetime($field_name, $date_format, $time_format);
1958
-    }
1959
-
1960
-
1961
-    /**
1962
-     * @param string $field_name
1963
-     * @param string $date_format
1964
-     * @param string $time_format
1965
-     * @throws InvalidArgumentException
1966
-     * @throws InvalidInterfaceException
1967
-     * @throws InvalidDataTypeException
1968
-     * @throws EE_Error
1969
-     */
1970
-    public function e_datetime($field_name, $date_format = '', $time_format = '')
1971
-    {
1972
-        $this->_get_datetime($field_name, $date_format, $time_format, null, true);
1973
-    }
1974
-
1975
-
1976
-    /**
1977
-     * Get the i8ln value for a date using the WordPress @param string $field_name The EE_Datetime_Field reference for
1978
-     *                           the date being retrieved.
1979
-     *
1980
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1981
-     *                           on the object will be used.
1982
-     * @return string Date and time string in set locale or false if no field exists for the given
1983
-     * @throws InvalidArgumentException
1984
-     * @throws InvalidInterfaceException
1985
-     * @throws InvalidDataTypeException
1986
-     * @throws EE_Error
1987
-     *                           field name.
1988
-     * @see date_i18n function.
1989
-     */
1990
-    public function get_i18n_datetime($field_name, $format = '')
1991
-    {
1992
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1993
-        return date_i18n(
1994
-            $format,
1995
-            EEH_DTT_Helper::get_timestamp_with_offset(
1996
-                $this->get_raw($field_name),
1997
-                $this->_timezone
1998
-            )
1999
-        );
2000
-    }
2001
-
2002
-
2003
-    /**
2004
-     * This method simply returns the RAW unprocessed value for the given property in this class
2005
-     *
2006
-     * @param string $field_name A valid field name
2007
-     * @return mixed              Whatever the raw value stored on the property is.
2008
-     * @throws InvalidArgumentException
2009
-     * @throws InvalidInterfaceException
2010
-     * @throws InvalidDataTypeException
2011
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
2012
-     */
2013
-    public function get_raw($field_name)
2014
-    {
2015
-        $field_settings = $this->_model->field_settings_for($field_name);
2016
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
2017
-            ? $this->_fields[ $field_name ]->format('U')
2018
-            : $this->_fields[ $field_name ];
2019
-    }
2020
-
2021
-
2022
-    /**
2023
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
2024
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
2025
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
2026
-     * that could lead to some unexpected results!
2027
-     *
2028
-     * @access public
2029
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
2030
-     *                                         value being returned.
2031
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
2032
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
2033
-     * @param string $prepend                  You can include something to prepend on the timestamp
2034
-     * @param string $append                   You can include something to append on the timestamp
2035
-     * @return string timestamp
2036
-     * @throws InvalidArgumentException
2037
-     * @throws InvalidInterfaceException
2038
-     * @throws InvalidDataTypeException
2039
-     * @throws EE_Error
2040
-     */
2041
-    public function display_in_my_timezone(
2042
-        $field_name,
2043
-        $callback = 'get_datetime',
2044
-        $args = null,
2045
-        $prepend = '',
2046
-        $append = ''
2047
-    ) {
2048
-        $timezone = EEH_DTT_Helper::get_timezone();
2049
-        if ($timezone === $this->_timezone) {
2050
-            return '';
2051
-        }
2052
-        $original_timezone = $this->_timezone;
2053
-        $this->set_timezone($timezone);
2054
-        $fn   = (array) $field_name;
2055
-        $args = array_merge($fn, (array) $args);
2056
-        if (! method_exists($this, $callback)) {
2057
-            throw new EE_Error(
2058
-                sprintf(
2059
-                    esc_html__(
2060
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
2061
-                        'event_espresso'
2062
-                    ),
2063
-                    $callback
2064
-                )
2065
-            );
2066
-        }
2067
-        $args   = (array) $args;
2068
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
2069
-        $this->set_timezone($original_timezone);
2070
-        return $return;
2071
-    }
2072
-
2073
-
2074
-    /**
2075
-     * Deletes this model object.
2076
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
2077
-     * override
2078
-     * `EE_Base_Class::_delete` NOT this class.
2079
-     *
2080
-     * @return boolean | int
2081
-     * @throws ReflectionException
2082
-     * @throws InvalidArgumentException
2083
-     * @throws InvalidInterfaceException
2084
-     * @throws InvalidDataTypeException
2085
-     * @throws EE_Error
2086
-     */
2087
-    public function delete()
2088
-    {
2089
-        /**
2090
-         * Called just before the `EE_Base_Class::_delete` method call.
2091
-         * Note:
2092
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2093
-         * should be aware that `_delete` may not always result in a permanent delete.
2094
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
2095
-         * soft deletes (trash) the object and does not permanently delete it.
2096
-         *
2097
-         * @param EE_Base_Class $model_object about to be 'deleted'
2098
-         */
2099
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
2100
-        $result = $this->_delete();
2101
-        /**
2102
-         * Called just after the `EE_Base_Class::_delete` method call.
2103
-         * Note:
2104
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2105
-         * should be aware that `_delete` may not always result in a permanent delete.
2106
-         * For example `EE_Soft_Base_Class::_delete`
2107
-         * soft deletes (trash) the object and does not permanently delete it.
2108
-         *
2109
-         * @param EE_Base_Class $model_object that was just 'deleted'
2110
-         * @param boolean       $result
2111
-         */
2112
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
2113
-        return $result;
2114
-    }
2115
-
2116
-
2117
-    /**
2118
-     * Calls the specific delete method for the instantiated class.
2119
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
2120
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
2121
-     * `EE_Base_Class::delete`
2122
-     *
2123
-     * @return bool|int
2124
-     * @throws ReflectionException
2125
-     * @throws InvalidArgumentException
2126
-     * @throws InvalidInterfaceException
2127
-     * @throws InvalidDataTypeException
2128
-     * @throws EE_Error
2129
-     */
2130
-    protected function _delete()
2131
-    {
2132
-        return $this->delete_permanently();
2133
-    }
2134
-
2135
-
2136
-    /**
2137
-     * Deletes this model object permanently from db
2138
-     * (but keep in mind related models may block the delete and return an error)
2139
-     *
2140
-     * @return bool | int
2141
-     * @throws ReflectionException
2142
-     * @throws InvalidArgumentException
2143
-     * @throws InvalidInterfaceException
2144
-     * @throws InvalidDataTypeException
2145
-     * @throws EE_Error
2146
-     */
2147
-    public function delete_permanently()
2148
-    {
2149
-        /**
2150
-         * Called just before HARD deleting a model object
2151
-         *
2152
-         * @param EE_Base_Class $model_object about to be 'deleted'
2153
-         */
2154
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
2155
-        $result = $this->_model->delete_permanently_by_ID($this->ID());
2156
-        $this->refresh_cache_of_related_objects();
2157
-        /**
2158
-         * Called just after HARD deleting a model object
2159
-         *
2160
-         * @param EE_Base_Class $model_object that was just 'deleted'
2161
-         * @param boolean       $result
2162
-         */
2163
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
2164
-        return $result;
2165
-    }
2166
-
2167
-
2168
-    /**
2169
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
2170
-     * related model objects
2171
-     *
2172
-     * @throws ReflectionException
2173
-     * @throws InvalidArgumentException
2174
-     * @throws InvalidInterfaceException
2175
-     * @throws InvalidDataTypeException
2176
-     * @throws EE_Error
2177
-     */
2178
-    public function refresh_cache_of_related_objects()
2179
-    {
2180
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
2181
-            if (! empty($this->_model_relations[ $relation_name ])) {
2182
-                $related_objects = $this->_model_relations[ $relation_name ];
2183
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
2184
-                    // this relation only stores a single model object, not an array
2185
-                    // but let's make it consistent
2186
-                    $related_objects = [$related_objects];
2187
-                }
2188
-                foreach ($related_objects as $related_object) {
2189
-                    // only refresh their cache if they're in memory
2190
-                    if ($related_object instanceof EE_Base_Class) {
2191
-                        $related_object->clear_cache(
2192
-                            $this->_model->get_this_model_name(),
2193
-                            $this
2194
-                        );
2195
-                    }
2196
-                }
2197
-            }
2198
-        }
2199
-    }
2200
-
2201
-
2202
-    /**
2203
-     * Forgets the cached model of the given relation Name. So the next time we request it,
2204
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
2205
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
2206
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
2207
-     *
2208
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
2209
-     *                                                     Eg 'Registration'
2210
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
2211
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
2212
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
2213
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
2214
-     *                                                     this is HasMany or HABTM.
2215
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
2216
-     *                                                     relation from all
2217
-     * @throws InvalidArgumentException
2218
-     * @throws InvalidInterfaceException
2219
-     * @throws InvalidDataTypeException
2220
-     * @throws EE_Error
2221
-     * @throws ReflectionException
2222
-     */
2223
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
2224
-    {
2225
-        $relationship_to_model = $this->_model->related_settings_for($relationName);
2226
-        $index_in_cache        = '';
2227
-        if (! $relationship_to_model) {
2228
-            throw new EE_Error(
2229
-                sprintf(
2230
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
2231
-                    $relationName,
2232
-                    get_class($this)
2233
-                )
2234
-            );
2235
-        }
2236
-        if ($clear_all) {
2237
-            $obj_removed                             = true;
2238
-            $this->_model_relations[ $relationName ] = null;
2239
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
2240
-            $obj_removed                             = $this->_model_relations[ $relationName ];
2241
-            $this->_model_relations[ $relationName ] = null;
2242
-        } else {
2243
-            if (
2244
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
2245
-                && $object_to_remove_or_index_into_array->ID()
2246
-            ) {
2247
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
2248
-                if (
2249
-                    is_array($this->_model_relations[ $relationName ])
2250
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
2251
-                ) {
2252
-                    $index_found_at = null;
2253
-                    // find this object in the array even though it has a different key
2254
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
2255
-                        /** @noinspection TypeUnsafeComparisonInspection */
2256
-                        if (
2257
-                            $obj instanceof EE_Base_Class
2258
-                            && (
2259
-                                $obj == $object_to_remove_or_index_into_array
2260
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
2261
-                            )
2262
-                        ) {
2263
-                            $index_found_at = $index;
2264
-                            break;
2265
-                        }
2266
-                    }
2267
-                    if ($index_found_at) {
2268
-                        $index_in_cache = $index_found_at;
2269
-                    } else {
2270
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
2271
-                        // if it wasn't in it to begin with. So we're done
2272
-                        return $object_to_remove_or_index_into_array;
2273
-                    }
2274
-                }
2275
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
2276
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
2277
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
2278
-                    /** @noinspection TypeUnsafeComparisonInspection */
2279
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
2280
-                        $index_in_cache = $index;
2281
-                    }
2282
-                }
2283
-            } else {
2284
-                $index_in_cache = $object_to_remove_or_index_into_array;
2285
-            }
2286
-            // supposedly we've found it. But it could just be that the client code
2287
-            // provided a bad index/object
2288
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
2289
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
2290
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
2291
-            } else {
2292
-                // that thing was never cached anyways.
2293
-                $obj_removed = null;
2294
-            }
2295
-        }
2296
-        return $obj_removed;
2297
-    }
2298
-
2299
-
2300
-    /**
2301
-     * Saves this model object and its NEW cached relations to the database.
2302
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
2303
-     * In order for that to work, we would need to mark model objects as dirty/clean...
2304
-     * because otherwise, there's a potential for infinite looping of saving
2305
-     * Saves the cached related model objects, and ensures the relation between them
2306
-     * and this object and properly setup
2307
-     *
2308
-     * @return int ID of new model object on save; 0 on failure+
2309
-     * @throws ReflectionException
2310
-     * @throws InvalidArgumentException
2311
-     * @throws InvalidInterfaceException
2312
-     * @throws InvalidDataTypeException
2313
-     * @throws EE_Error
2314
-     */
2315
-    public function save_new_cached_related_model_objs()
2316
-    {
2317
-        // make sure this has been saved
2318
-        if (! $this->ID()) {
2319
-            $id = $this->save();
2320
-        } else {
2321
-            $id = $this->ID();
2322
-        }
2323
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
2324
-        foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
2325
-            if ($this->_model_relations[ $relationName ]) {
2326
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2327
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2328
-                /* @var $related_model_obj EE_Base_Class */
2329
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
2330
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
2331
-                    // but ONLY if it DOES NOT exist in the DB
2332
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2333
-                    // if( ! $related_model_obj->ID()){
2334
-                    $this->_add_relation_to($related_model_obj, $relationName);
2335
-                    $related_model_obj->save_new_cached_related_model_objs();
2336
-                    // }
2337
-                } else {
2338
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2339
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
2340
-                        // but ONLY if it DOES NOT exist in the DB
2341
-                        // if( ! $related_model_obj->ID()){
2342
-                        $this->_add_relation_to($related_model_obj, $relationName);
2343
-                        $related_model_obj->save_new_cached_related_model_objs();
2344
-                        // }
2345
-                    }
2346
-                }
2347
-            }
2348
-        }
2349
-        return $id;
2350
-    }
2351
-
2352
-
2353
-    /**
2354
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2355
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2356
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2357
-     *
2358
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2359
-     * @param string $relationName                     eg 'Events','Question',etc.
2360
-     *                                                 an attendee to a group, you also want to specify which role they
2361
-     *                                                 will have in that group. So you would use this parameter to
2362
-     *                                                 specify array('role-column-name'=>'role-id')
2363
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2364
-     *                                                 allow you to further constrict the relation to being added.
2365
-     *                                                 However, keep in mind that the columns (keys) given must match a
2366
-     *                                                 column on the JOIN table and currently only the HABTM models
2367
-     *                                                 accept these additional conditions.  Also remember that if an
2368
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2369
-     *                                                 NEW row is created in the join table.
2370
-     * @param null   $cache_id
2371
-     * @return EE_Base_Class the object the relation was added to
2372
-     * @throws ReflectionException
2373
-     * @throws InvalidArgumentException
2374
-     * @throws InvalidInterfaceException
2375
-     * @throws InvalidDataTypeException
2376
-     * @throws EE_Error
2377
-     */
2378
-    public function _add_relation_to(
2379
-        $otherObjectModelObjectOrID,
2380
-        $relationName,
2381
-        $extra_join_model_fields_n_values = [],
2382
-        $cache_id = null
2383
-    ) {
2384
-        // if this thing exists in the DB, save the relation to the DB
2385
-        if ($this->ID()) {
2386
-            $otherObject = $this->_model->add_relationship_to(
2387
-                $this,
2388
-                $otherObjectModelObjectOrID,
2389
-                $relationName,
2390
-                $extra_join_model_fields_n_values
2391
-            );
2392
-            // clear cache so future get_many_related and get_first_related() return new results.
2393
-            $this->clear_cache($relationName, $otherObject, true);
2394
-            if ($otherObject instanceof EE_Base_Class) {
2395
-                $otherObject->clear_cache($this->_model->get_this_model_name(), $this);
2396
-            }
2397
-        } else {
2398
-            // this thing doesn't exist in the DB,  so just cache it
2399
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2400
-                throw new EE_Error(
2401
-                    sprintf(
2402
-                        esc_html__(
2403
-                            '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',
2404
-                            'event_espresso'
2405
-                        ),
2406
-                        $otherObjectModelObjectOrID,
2407
-                        get_class($this)
2408
-                    )
2409
-                );
2410
-            }
2411
-            $otherObject = $otherObjectModelObjectOrID;
2412
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2413
-        }
2414
-        if ($otherObject instanceof EE_Base_Class) {
2415
-            // fix the reciprocal relation too
2416
-            if ($otherObject->ID()) {
2417
-                // its saved so assumed relations exist in the DB, so we can just
2418
-                // clear the cache so future queries use the updated info in the DB
2419
-                $otherObject->clear_cache(
2420
-                    $this->_model->get_this_model_name(),
2421
-                    null,
2422
-                    true
2423
-                );
2424
-            } else {
2425
-                // it's not saved, so it caches relations like this
2426
-                $otherObject->cache($this->_model->get_this_model_name(), $this);
2427
-            }
2428
-        }
2429
-        return $otherObject;
2430
-    }
2431
-
2432
-
2433
-    /**
2434
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2435
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2436
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2437
-     * from the cache
2438
-     *
2439
-     * @param mixed  $otherObjectModelObjectOrID
2440
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2441
-     *                to the DB yet
2442
-     * @param string $relationName
2443
-     * @param array  $where_query
2444
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2445
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2446
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2447
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2448
-     *                deleted.
2449
-     * @return EE_Base_Class the relation was removed from
2450
-     * @throws ReflectionException
2451
-     * @throws InvalidArgumentException
2452
-     * @throws InvalidInterfaceException
2453
-     * @throws InvalidDataTypeException
2454
-     * @throws EE_Error
2455
-     */
2456
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
2457
-    {
2458
-        if ($this->ID()) {
2459
-            // if this exists in the DB, save the relation change to the DB too
2460
-            $otherObject = $this->_model->remove_relationship_to(
2461
-                $this,
2462
-                $otherObjectModelObjectOrID,
2463
-                $relationName,
2464
-                $where_query
2465
-            );
2466
-            $this->clear_cache(
2467
-                $relationName,
2468
-                $otherObject
2469
-            );
2470
-        } else {
2471
-            // this doesn't exist in the DB, just remove it from the cache
2472
-            $otherObject = $this->clear_cache(
2473
-                $relationName,
2474
-                $otherObjectModelObjectOrID
2475
-            );
2476
-        }
2477
-        if ($otherObject instanceof EE_Base_Class) {
2478
-            $otherObject->clear_cache(
2479
-                $this->_model->get_this_model_name(),
2480
-                $this
2481
-            );
2482
-        }
2483
-        return $otherObject;
2484
-    }
2485
-
2486
-
2487
-    /**
2488
-     * Removes ALL the related things for the $relationName.
2489
-     *
2490
-     * @param string $relationName
2491
-     * @param array  $where_query_params @see
2492
-     *                                   https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2493
-     * @return EE_Base_Class
2494
-     * @throws ReflectionException
2495
-     * @throws InvalidArgumentException
2496
-     * @throws InvalidInterfaceException
2497
-     * @throws InvalidDataTypeException
2498
-     * @throws EE_Error
2499
-     */
2500
-    public function _remove_relations($relationName, $where_query_params = [])
2501
-    {
2502
-        if ($this->ID()) {
2503
-            // if this exists in the DB, save the relation change to the DB too
2504
-            $otherObjects = $this->_model->remove_relations(
2505
-                $this,
2506
-                $relationName,
2507
-                $where_query_params
2508
-            );
2509
-            $this->clear_cache(
2510
-                $relationName,
2511
-                null,
2512
-                true
2513
-            );
2514
-        } else {
2515
-            // this doesn't exist in the DB, just remove it from the cache
2516
-            $otherObjects = $this->clear_cache(
2517
-                $relationName,
2518
-                null,
2519
-                true
2520
-            );
2521
-        }
2522
-        if (is_array($otherObjects)) {
2523
-            foreach ($otherObjects as $otherObject) {
2524
-                $otherObject->clear_cache(
2525
-                    $this->_model->get_this_model_name(),
2526
-                    $this
2527
-                );
2528
-            }
2529
-        }
2530
-        return $otherObjects;
2531
-    }
2532
-
2533
-
2534
-    /**
2535
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2536
-     * unless otherwise specified in the $query_params
2537
-     *
2538
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2539
-     * @param array  $query_params   @see
2540
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2541
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2542
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2543
-     *                               that by the setting $distinct to TRUE;
2544
-     * @return int
2545
-     * @throws ReflectionException
2546
-     * @throws InvalidArgumentException
2547
-     * @throws InvalidInterfaceException
2548
-     * @throws InvalidDataTypeException
2549
-     * @throws EE_Error
2550
-     */
2551
-    public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2552
-    {
2553
-        return $this->_model->count_related(
2554
-            $this,
2555
-            $relation_name,
2556
-            $query_params,
2557
-            $field_to_count,
2558
-            $distinct
2559
-        );
2560
-    }
2561
-
2562
-
2563
-    /**
2564
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2565
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2566
-     *
2567
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2568
-     * @param array  $query_params  @see
2569
-     *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2570
-     * @param string $field_to_sum  name of field to count by.
2571
-     *                              By default, uses primary key
2572
-     *                              (which doesn't make much sense, so you should probably change it)
2573
-     * @return int
2574
-     * @throws ReflectionException
2575
-     * @throws InvalidArgumentException
2576
-     * @throws InvalidInterfaceException
2577
-     * @throws InvalidDataTypeException
2578
-     * @throws EE_Error
2579
-     */
2580
-    public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2581
-    {
2582
-        return $this->_model->sum_related(
2583
-            $this,
2584
-            $relation_name,
2585
-            $query_params,
2586
-            $field_to_sum
2587
-        );
2588
-    }
2589
-
2590
-
2591
-    /**
2592
-     * Does a delete on all related objects of type $relationName and removes
2593
-     * the current model object's relation to them. If they can't be deleted (because
2594
-     * of blocking related model objects) does nothing. If the related model objects are
2595
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2596
-     * If this model object doesn't exist yet in the DB, just removes its related things
2597
-     *
2598
-     * @param string $relationName
2599
-     * @param array  $query_params @see
2600
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2601
-     * @return int how many deleted
2602
-     * @throws ReflectionException
2603
-     * @throws InvalidArgumentException
2604
-     * @throws InvalidInterfaceException
2605
-     * @throws InvalidDataTypeException
2606
-     * @throws EE_Error
2607
-     */
2608
-    public function delete_related($relationName, $query_params = [])
2609
-    {
2610
-        if ($this->ID()) {
2611
-            $count = $this->_model->delete_related(
2612
-                $this,
2613
-                $relationName,
2614
-                $query_params
2615
-            );
2616
-        } else {
2617
-            $count = count($this->get_all_from_cache($relationName));
2618
-            $this->clear_cache($relationName, null, true);
2619
-        }
2620
-        return $count;
2621
-    }
2622
-
2623
-
2624
-    /**
2625
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2626
-     * the current model object's relation to them. If they can't be deleted (because
2627
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2628
-     * If the related thing isn't a soft-deletable model object, this function is identical
2629
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2630
-     *
2631
-     * @param string $relationName
2632
-     * @param array  $query_params @see
2633
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2634
-     * @return int how many deleted (including those soft deleted)
2635
-     * @throws ReflectionException
2636
-     * @throws InvalidArgumentException
2637
-     * @throws InvalidInterfaceException
2638
-     * @throws InvalidDataTypeException
2639
-     * @throws EE_Error
2640
-     */
2641
-    public function delete_related_permanently($relationName, $query_params = [])
2642
-    {
2643
-        if ($this->ID()) {
2644
-            $count = $this->_model->delete_related_permanently(
2645
-                $this,
2646
-                $relationName,
2647
-                $query_params
2648
-            );
2649
-        } else {
2650
-            $count = count($this->get_all_from_cache($relationName));
2651
-        }
2652
-        $this->clear_cache($relationName, null, true);
2653
-        return $count;
2654
-    }
2655
-
2656
-
2657
-    /**
2658
-     * is_set
2659
-     * Just a simple utility function children can use for checking if property exists
2660
-     *
2661
-     * @access  public
2662
-     * @param string $field_name property to check
2663
-     * @return bool                              TRUE if existing,FALSE if not.
2664
-     */
2665
-    public function is_set($field_name)
2666
-    {
2667
-        return isset($this->_fields[ $field_name ]);
2668
-    }
2669
-
2670
-
2671
-    /**
2672
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2673
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2674
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2675
-     * Instead of requiring a plugin to extend the EE_Base_Class
2676
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2677
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2678
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2679
-     * and accepts 2 arguments: the object on which the function was called,
2680
-     * and an array of the original arguments passed to the function.
2681
-     * Whatever their callback function returns will be returned by this function.
2682
-     * Example: in functions.php (or in a plugin):
2683
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2684
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2685
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2686
-     *          return $previousReturnValue.$returnString;
2687
-     *      }
2688
-     * require('EE_Answer.class.php');
2689
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2690
-     * echo $answer->my_callback('monkeys',100);
2691
-     * //will output "you called my_callback! and passed args:monkeys,100"
2692
-     *
2693
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2694
-     * @param array  $args       array of original arguments passed to the function
2695
-     * @return mixed whatever the plugin which calls add_filter decides
2696
-     * @throws EE_Error
2697
-     */
2698
-    public function __call($methodName, $args)
2699
-    {
2700
-        $className = get_class($this);
2701
-        $tagName   = "FHEE__{$className}__{$methodName}";
2702
-        if (! has_filter($tagName)) {
2703
-            throw new EE_Error(
2704
-                sprintf(
2705
-                    esc_html__(
2706
-                        "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;}",
2707
-                        'event_espresso'
2708
-                    ),
2709
-                    $methodName,
2710
-                    $className,
2711
-                    $tagName
2712
-                )
2713
-            );
2714
-        }
2715
-        return apply_filters($tagName, null, $this, $args);
2716
-    }
2717
-
2718
-
2719
-    /**
2720
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2721
-     * is specified, only deletes extra meta records with that value.
2722
-     *
2723
-     * @param string $meta_key
2724
-     * @param mixed  $meta_value
2725
-     * @return int number of extra meta rows deleted
2726
-     * @throws InvalidArgumentException
2727
-     * @throws InvalidInterfaceException
2728
-     * @throws InvalidDataTypeException
2729
-     * @throws EE_Error
2730
-     * @throws ReflectionException
2731
-     */
2732
-    public function delete_extra_meta($meta_key, $meta_value = null)
2733
-    {
2734
-        $query_params = [
2735
-            [
2736
-                'EXM_key'  => $meta_key,
2737
-                'OBJ_ID'   => $this->ID(),
2738
-                'EXM_type' => $this->_model->get_this_model_name(),
2739
-            ],
2740
-        ];
2741
-        if ($meta_value !== null) {
2742
-            $query_params[0]['EXM_value'] = $meta_value;
2743
-        }
2744
-        return EEM_Extra_Meta::instance()->delete($query_params);
2745
-    }
2746
-
2747
-
2748
-    /**
2749
-     * Returns a simple array of all the extra meta associated with this model object.
2750
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2751
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2752
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2753
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2754
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2755
-     * finally the extra meta's value as each sub-value. (eg
2756
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2757
-     *
2758
-     * @param boolean $one_of_each_key
2759
-     * @return array
2760
-     * @throws ReflectionException
2761
-     * @throws InvalidArgumentException
2762
-     * @throws InvalidInterfaceException
2763
-     * @throws InvalidDataTypeException
2764
-     * @throws EE_Error
2765
-     */
2766
-    public function all_extra_meta_array($one_of_each_key = true)
2767
-    {
2768
-        $return_array = [];
2769
-        if ($one_of_each_key) {
2770
-            $extra_meta_objs = $this->get_many_related(
2771
-                'Extra_Meta',
2772
-                ['group_by' => 'EXM_key']
2773
-            );
2774
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2775
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2776
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2777
-                }
2778
-            }
2779
-        } else {
2780
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2781
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2782
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2783
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2784
-                        $return_array[ $extra_meta_obj->key() ] = [];
2785
-                    }
2786
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2787
-                }
2788
-            }
2789
-        }
2790
-        return $return_array;
2791
-    }
2792
-
2793
-
2794
-    /**
2795
-     * refresh_from_db
2796
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
2797
-     *
2798
-     * @throws ReflectionException
2799
-     * @throws InvalidArgumentException
2800
-     * @throws InvalidInterfaceException
2801
-     * @throws InvalidDataTypeException
2802
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2803
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2804
-     */
2805
-    public function refresh_from_db()
2806
-    {
2807
-        if ($this->ID() && $this->in_entity_map()) {
2808
-            $this->_model->refresh_entity_map_from_db($this->ID());
2809
-        } else {
2810
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2811
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
2812
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2813
-            // absolutely nothing in it for this ID
2814
-            if (WP_DEBUG) {
2815
-                throw new EE_Error(
2816
-                    sprintf(
2817
-                        esc_html__(
2818
-                            '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.',
2819
-                            'event_espresso'
2820
-                        ),
2821
-                        $this->ID(),
2822
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2823
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2824
-                    )
2825
-                );
2826
-            }
2827
-        }
2828
-    }
2829
-
2830
-
2831
-    /**
2832
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
2833
-     * Does not allow negative values, however.
2834
-     *
2835
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
2836
-     *                                   (positive or negative). One important gotcha: all these values must be
2837
-     *                                   on the same table (eg don't pass in one field for the posts table and
2838
-     *                                   another for the event meta table.)
2839
-     * @return bool
2840
-     * @throws EE_Error
2841
-     * @throws InvalidArgumentException
2842
-     * @throws InvalidDataTypeException
2843
-     * @throws InvalidInterfaceException
2844
-     * @throws ReflectionException
2845
-     * @since 4.9.80.p
2846
-     */
2847
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
2848
-    {
2849
-        global $wpdb;
2850
-        if (empty($fields_n_quantities)) {
2851
-            // No fields to update? Well sure, we updated them to that value just fine.
2852
-            return true;
2853
-        }
2854
-        $fields             = [];
2855
-        $set_sql_statements = [];
2856
-        foreach ($fields_n_quantities as $field_name => $quantity) {
2857
-            $field       = $this->_model->field_settings_for($field_name);
2858
-            $fields[]    = $field;
2859
-            $column_name = $field->get_table_column();
2860
-
2861
-            $abs_qty = absint($quantity);
2862
-            if ($quantity > 0) {
2863
-                // don't let the value be negative as often these fields are unsigned
2864
-                $set_sql_statements[] = $wpdb->prepare(
2865
-                    "`{$column_name}` = `{$column_name}` + %d",
2866
-                    $abs_qty
2867
-                );
2868
-            } else {
2869
-                $set_sql_statements[] = $wpdb->prepare(
2870
-                    "`{$column_name}` = CASE
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   $fieldValues                             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 boolean $by_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($fieldValues = [], $by_db = false, $timezone = '', $date_formats = [])
141
+	{
142
+		$className = get_class($this);
143
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
144
+		$this->_model = $this->get_model();
145
+		$model_fields = $this->_model->field_settings();
146
+		// ensure $fieldValues is an array
147
+		$fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
148
+		// verify client code has not passed any invalid field names
149
+		foreach ($fieldValues as $field_name => $field_value) {
150
+			if (! isset($model_fields[ $field_name ])) {
151
+				throw new EE_Error(
152
+					sprintf(
153
+						esc_html__(
154
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
155
+							'event_espresso'
156
+						),
157
+						$field_name,
158
+						get_class($this),
159
+						implode(', ', array_keys($model_fields))
160
+					)
161
+				);
162
+			}
163
+		}
164
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
165
+		if (! empty($date_formats) && is_array($date_formats)) {
166
+			list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
167
+		} else {
168
+			// set default formats for date and time
169
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
170
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
171
+		}
172
+		// if db model is instantiating
173
+		if ($by_db) {
174
+			// client code has indicated these field values are from the database
175
+			foreach ($model_fields as $fieldName => $field) {
176
+				$this->set_from_db(
177
+					$fieldName,
178
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
179
+				);
180
+			}
181
+		} else {
182
+			// we're constructing a brand
183
+			// new instance of the model object. Generally, this means we'll need to do more field validation
184
+			foreach ($model_fields as $fieldName => $field) {
185
+				$this->set(
186
+					$fieldName,
187
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
188
+					true
189
+				);
190
+			}
191
+		}
192
+		// remember what values were passed to this constructor
193
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
194
+		// remember in entity mapper
195
+		if (! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
196
+			$this->_model->add_to_entity_map($this);
197
+		}
198
+		// setup all the relations
199
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
200
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
+				$this->_model_relations[ $relation_name ] = null;
202
+			} else {
203
+				$this->_model_relations[ $relation_name ] = [];
204
+			}
205
+		}
206
+		/**
207
+		 * Action done at the end of each model object construction
208
+		 *
209
+		 * @param EE_Base_Class $this the model object just created
210
+		 */
211
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
+	}
213
+
214
+
215
+	/**
216
+	 * for getting a model while instantiated.
217
+	 *
218
+	 * @return EEM_Base | EEM_CPT_Base
219
+	 * @throws ReflectionException
220
+	 * @throws InvalidArgumentException
221
+	 * @throws InvalidInterfaceException
222
+	 * @throws InvalidDataTypeException
223
+	 * @throws EE_Error
224
+	 */
225
+	public function get_model()
226
+	{
227
+		if (! $this->_model) {
228
+			$modelName    = self::_get_model_classname(get_class($this));
229
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
230
+		}
231
+		return $this->_model;
232
+	}
233
+
234
+
235
+	/**
236
+	 * Overrides parent because parent expects old models.
237
+	 * This also doesn't do any validation, and won't work for serialized arrays
238
+	 *
239
+	 * @param string $field_name
240
+	 * @param mixed  $field_value_from_db
241
+	 * @throws InvalidArgumentException
242
+	 * @throws InvalidInterfaceException
243
+	 * @throws InvalidDataTypeException
244
+	 * @throws EE_Error
245
+	 */
246
+	public function set_from_db($field_name, $field_value_from_db)
247
+	{
248
+		$field_obj = $this->_model->field_settings_for($field_name);
249
+		if ($field_obj instanceof EE_Model_Field_Base) {
250
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
251
+			// eg, a CPT model object could have an entry in the posts table, but no
252
+			// entry in the meta table. Meaning that all its columns in the meta table
253
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
254
+			if ($field_value_from_db === null) {
255
+				if ($field_obj->is_nullable()) {
256
+					// if the field allows nulls, then let it be null
257
+					$field_value = null;
258
+				} else {
259
+					$field_value = $field_obj->get_default_value();
260
+				}
261
+			} else {
262
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
263
+			}
264
+			$this->_fields[ $field_name ] = $field_value;
265
+			$this->_clear_cached_property($field_name);
266
+		}
267
+	}
268
+
269
+
270
+	/**
271
+	 * Overrides parent because parent expects old models.
272
+	 * This also doesn't do any validation, and won't work for serialized arrays
273
+	 *
274
+	 * @param string $field_name
275
+	 * @param mixed  $field_value
276
+	 * @param bool   $use_default
277
+	 * @throws InvalidArgumentException
278
+	 * @throws InvalidInterfaceException
279
+	 * @throws InvalidDataTypeException
280
+	 * @throws EE_Error
281
+	 * @throws ReflectionException
282
+	 * @throws ReflectionException
283
+	 * @throws ReflectionException
284
+	 */
285
+	public function set($field_name, $field_value, $use_default = false)
286
+	{
287
+		// if not using default and nothing has changed, and object has already been setup (has ID),
288
+		// then don't do anything
289
+		if (
290
+			! $use_default
291
+			&& $this->_fields[ $field_name ] === $field_value
292
+			&& $this->ID()
293
+		) {
294
+			return;
295
+		}
296
+		$this->_has_changes = true;
297
+		$field_obj          = $this->_model->field_settings_for($field_name);
298
+		if ($field_obj instanceof EE_Model_Field_Base) {
299
+			// if ( method_exists( $field_obj, 'set_timezone' )) {
300
+			if ($field_obj instanceof EE_Datetime_Field) {
301
+				$field_obj->set_timezone($this->_timezone);
302
+				$field_obj->set_date_format($this->_dt_frmt);
303
+				$field_obj->set_time_format($this->_tm_frmt);
304
+			}
305
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
306
+			// should the value be null?
307
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
308
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
309
+				/**
310
+				 * To save having to refactor all the models, if a default value is used for a
311
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
312
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
313
+				 * object.
314
+				 *
315
+				 * @since 4.6.10+
316
+				 */
317
+				if (
318
+					$field_obj instanceof EE_Datetime_Field
319
+					&& $this->_fields[ $field_name ] !== null
320
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
321
+				) {
322
+					empty($this->_fields[ $field_name ])
323
+						? $this->set($field_name, time())
324
+						: $this->set($field_name, $this->_fields[ $field_name ]);
325
+				}
326
+			} else {
327
+				$this->_fields[ $field_name ] = $holder_of_value;
328
+			}
329
+			// if we're not in the constructor...
330
+			// now check if what we set was a primary key
331
+			if (
332
+				// note: props_n_values_provided_in_constructor is only set at the END of the constructor
333
+				$this->_props_n_values_provided_in_constructor
334
+				&& $field_value
335
+				&& $field_name === $this->_model->primary_key_name()
336
+			) {
337
+				// if so, we want all this object's fields to be filled either with
338
+				// what we've explicitly set on this model
339
+				// or what we have in the db
340
+				// echo "setting primary key!";
341
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
342
+				$obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
343
+				foreach ($fields_on_model as $field_obj) {
344
+					if (
345
+						! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
346
+						&& $field_obj->get_name() !== $field_name
347
+					) {
348
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
349
+					}
350
+				}
351
+				// oh this model object has an ID? well make sure its in the entity mapper
352
+				$this->_model->add_to_entity_map($this);
353
+			}
354
+			// let's unset any cache for this field_name from the $_cached_properties property.
355
+			$this->_clear_cached_property($field_name);
356
+		} else {
357
+			throw new EE_Error(
358
+				sprintf(
359
+					esc_html__(
360
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
361
+						'event_espresso'
362
+					),
363
+					$field_name
364
+				)
365
+			);
366
+		}
367
+	}
368
+
369
+
370
+	/**
371
+	 * Gets the value of the primary key.
372
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
373
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
374
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
375
+	 *
376
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
377
+	 * @throws InvalidArgumentException
378
+	 * @throws InvalidInterfaceException
379
+	 * @throws InvalidDataTypeException
380
+	 * @throws EE_Error
381
+	 */
382
+	public function ID()
383
+	{
384
+		// now that we know the name of the variable, use a variable variable to get its value and return its
385
+		if ($this->_model->has_primary_key_field()) {
386
+			return $this->_fields[ $this->_model->primary_key_name() ];
387
+		}
388
+		return $this->_model->get_index_primary_key_string($this->_fields);
389
+	}
390
+
391
+
392
+	/**
393
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
394
+	 * Also works if a model class's classname is provided (eg EE_Registration).
395
+	 *
396
+	 * @param null $model_name
397
+	 * @return string like EEM_Attendee
398
+	 */
399
+	private static function _get_model_classname($model_name = null)
400
+	{
401
+		if (strpos($model_name, 'EE_') === 0) {
402
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
403
+		} else {
404
+			$model_classname = 'EEM_' . $model_name;
405
+		}
406
+		return $model_classname;
407
+	}
408
+
409
+
410
+	/**
411
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
412
+	 *
413
+	 * @param string $model_classname
414
+	 * @param null   $timezone
415
+	 * @return EEM_Base
416
+	 * @throws ReflectionException
417
+	 * @throws InvalidArgumentException
418
+	 * @throws InvalidInterfaceException
419
+	 * @throws InvalidDataTypeException
420
+	 * @throws EE_Error
421
+	 */
422
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
423
+	{
424
+		$model_classname = str_replace('EEM_', '', $model_classname);
425
+		$model           = EE_Registry::instance()->load_model($model_classname);
426
+		$model->set_timezone($timezone);
427
+		return $model;
428
+	}
429
+
430
+
431
+	/**
432
+	 * This just clears out ONE property if it exists in the cache
433
+	 *
434
+	 * @param string $property_name the property to remove if it exists (from the _cached_properties array)
435
+	 * @return void
436
+	 */
437
+	protected function _clear_cached_property($property_name)
438
+	{
439
+		if (isset($this->_cached_properties[ $property_name ])) {
440
+			unset($this->_cached_properties[ $property_name ]);
441
+		}
442
+	}
443
+
444
+
445
+	/**
446
+	 * Gets the EEM_*_Model for this class
447
+	 *
448
+	 * @access public now, as this is more convenient
449
+	 * @param      $classname
450
+	 * @param null $timezone
451
+	 * @return EEM_Base
452
+	 * @throws InvalidArgumentException
453
+	 * @throws InvalidInterfaceException
454
+	 * @throws InvalidDataTypeException
455
+	 * @throws EE_Error
456
+	 * @throws ReflectionException
457
+	 */
458
+	protected static function _get_model($classname, $timezone = null)
459
+	{
460
+		// find model for this class
461
+		if (! $classname) {
462
+			throw new EE_Error(
463
+				sprintf(
464
+					esc_html__(
465
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
466
+						'event_espresso'
467
+					),
468
+					$classname
469
+				)
470
+			);
471
+		}
472
+		$modelName = self::_get_model_classname($classname);
473
+		return self::_get_model_instance_with_name($modelName, $timezone);
474
+	}
475
+
476
+
477
+	/**
478
+	 * verifies that the specified field is of the correct type
479
+	 *
480
+	 * @param string $field_name
481
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
482
+	 *                                (in cases where the same property may be used for different outputs
483
+	 *                                - i.e. datetime, money etc.)
484
+	 * @return mixed
485
+	 * @throws InvalidArgumentException
486
+	 * @throws InvalidInterfaceException
487
+	 * @throws InvalidDataTypeException
488
+	 * @throws EE_Error
489
+	 */
490
+	public function get($field_name, $extra_cache_ref = null)
491
+	{
492
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
493
+	}
494
+
495
+
496
+	/**
497
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
498
+	 * This also SETS the cache if we return the actual property!
499
+	 *
500
+	 * @param string $field_name       the name of the property we're trying to retrieve
501
+	 * @param bool   $pretty
502
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
503
+	 *                                 (in cases where the same property may be used for different outputs
504
+	 *                                 - i.e. datetime, money etc.)
505
+	 *                                 It can also accept certain pre-defined "schema" strings
506
+	 *                                 to define how to output the property.
507
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
508
+	 * @return mixed                   whatever the value for the property is we're retrieving
509
+	 * @throws InvalidArgumentException
510
+	 * @throws InvalidInterfaceException
511
+	 * @throws InvalidDataTypeException
512
+	 * @throws EE_Error
513
+	 */
514
+	protected function _get_cached_property($field_name, $pretty = false, $extra_cache_ref = null)
515
+	{
516
+		// verify the field exists
517
+		$this->_model->field_settings_for($field_name);
518
+		$cache_type = $pretty ? 'pretty' : 'standard';
519
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
520
+		if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
521
+			return $this->_cached_properties[ $field_name ][ $cache_type ];
522
+		}
523
+		$value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
524
+		$this->_set_cached_property($field_name, $value, $cache_type);
525
+		return $value;
526
+	}
527
+
528
+
529
+	/**
530
+	 * If the cache didn't fetch the needed item, this fetches it.
531
+	 *
532
+	 * @param string $field_name
533
+	 * @param bool   $pretty
534
+	 * @param string $extra_cache_ref
535
+	 * @return mixed
536
+	 * @throws InvalidArgumentException
537
+	 * @throws InvalidInterfaceException
538
+	 * @throws InvalidDataTypeException
539
+	 * @throws EE_Error
540
+	 */
541
+	protected function _get_fresh_property($field_name, $pretty = false, $extra_cache_ref = null)
542
+	{
543
+		$field_obj = $this->_model->field_settings_for($field_name);
544
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
545
+		if ($field_obj instanceof EE_Datetime_Field) {
546
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
547
+		}
548
+		if (! isset($this->_fields[ $field_name ])) {
549
+			$this->_fields[ $field_name ] = null;
550
+		}
551
+		return $pretty
552
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
553
+			: $field_obj->prepare_for_get($this->_fields[ $field_name ]);
554
+	}
555
+
556
+
557
+	/**
558
+	 * For adding an item to the cached_properties property.
559
+	 *
560
+	 * @access protected
561
+	 * @param string      $field_name the property item the corresponding value is for.
562
+	 * @param mixed       $value      The value we are caching.
563
+	 * @param string|null $cache_type
564
+	 * @return void
565
+	 * @throws InvalidArgumentException
566
+	 * @throws InvalidInterfaceException
567
+	 * @throws InvalidDataTypeException
568
+	 * @throws EE_Error
569
+	 */
570
+	protected function _set_cached_property($field_name, $value, $cache_type = null)
571
+	{
572
+		// first make sure this property exists
573
+		$this->_model->field_settings_for($field_name);
574
+		$cache_type                                             = empty($cache_type) ? 'standard' : $cache_type;
575
+		$this->_cached_properties[ $field_name ][ $cache_type ] = $value;
576
+	}
577
+
578
+
579
+	/**
580
+	 * set timezone, formats, and output for EE_Datetime_Field objects
581
+	 *
582
+	 * @param EE_Datetime_Field $datetime_field
583
+	 * @param bool              $pretty
584
+	 * @param null              $date_or_time
585
+	 * @return void
586
+	 * @throws InvalidArgumentException
587
+	 * @throws InvalidInterfaceException
588
+	 * @throws InvalidDataTypeException
589
+	 */
590
+	protected function _prepare_datetime_field(
591
+		EE_Datetime_Field $datetime_field,
592
+		$pretty = false,
593
+		$date_or_time = null
594
+	) {
595
+		$datetime_field->set_timezone($this->_timezone);
596
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
597
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
598
+		// set the output returned
599
+		switch ($date_or_time) {
600
+			case 'D':
601
+				$datetime_field->set_date_time_output('date');
602
+				break;
603
+			case 'T':
604
+				$datetime_field->set_date_time_output('time');
605
+				break;
606
+			default:
607
+				$datetime_field->set_date_time_output();
608
+		}
609
+	}
610
+
611
+
612
+	/**
613
+	 * @param $props_n_values
614
+	 * @param $classname
615
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
616
+	 * @throws ReflectionException
617
+	 * @throws InvalidArgumentException
618
+	 * @throws InvalidInterfaceException
619
+	 * @throws InvalidDataTypeException
620
+	 * @throws EE_Error
621
+	 */
622
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
623
+	{
624
+		// TODO: will not work for Term_Relationships because they have no PK!
625
+		$primary_id_ref = self::_get_primary_key_name($classname);
626
+		if (
627
+			array_key_exists($primary_id_ref, $props_n_values)
628
+			&& ! empty($props_n_values[ $primary_id_ref ])
629
+		) {
630
+			$id = $props_n_values[ $primary_id_ref ];
631
+			return self::_get_model($classname)->get_from_entity_map($id);
632
+		}
633
+		return false;
634
+	}
635
+
636
+
637
+	/**
638
+	 * returns the name of the primary key attribute
639
+	 *
640
+	 * @param null $classname
641
+	 * @return string
642
+	 * @throws InvalidArgumentException
643
+	 * @throws InvalidInterfaceException
644
+	 * @throws InvalidDataTypeException
645
+	 * @throws EE_Error
646
+	 * @throws ReflectionException
647
+	 */
648
+	protected static function _get_primary_key_name($classname = null)
649
+	{
650
+		if (! $classname) {
651
+			throw new EE_Error(
652
+				sprintf(
653
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
654
+					$classname
655
+				)
656
+			);
657
+		}
658
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
659
+	}
660
+
661
+
662
+	/**
663
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
664
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
665
+	 * 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
666
+	 * we return false.
667
+	 *
668
+	 * @param array  $props_n_values    incoming array of properties and their values
669
+	 * @param string $classname         the classname of the child class
670
+	 * @param null   $timezone
671
+	 * @param array  $date_formats      incoming date_formats in an array where the first value is the
672
+	 *                                  date_format and the second value is the time format
673
+	 * @return mixed (EE_Base_Class|bool)
674
+	 * @throws InvalidArgumentException
675
+	 * @throws InvalidInterfaceException
676
+	 * @throws InvalidDataTypeException
677
+	 * @throws EE_Error
678
+	 * @throws ReflectionException
679
+	 * @throws ReflectionException
680
+	 * @throws ReflectionException
681
+	 */
682
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = [])
683
+	{
684
+		$existing = null;
685
+		$model    = self::_get_model($classname, $timezone);
686
+		if ($model->has_primary_key_field()) {
687
+			$primary_id_ref = self::_get_primary_key_name($classname);
688
+			if (
689
+				array_key_exists($primary_id_ref, $props_n_values)
690
+				&& ! empty($props_n_values[ $primary_id_ref ])
691
+			) {
692
+				$existing = $model->get_one_by_ID(
693
+					$props_n_values[ $primary_id_ref ]
694
+				);
695
+			}
696
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
697
+			// no primary key on this model, but there's still a matching item in the DB
698
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
699
+				self::_get_model($classname, $timezone)
700
+					->get_index_primary_key_string($props_n_values)
701
+			);
702
+		}
703
+		if ($existing) {
704
+			// set date formats if present before setting values
705
+			if (! empty($date_formats) && is_array($date_formats)) {
706
+				$existing->set_date_format($date_formats[0]);
707
+				$existing->set_time_format($date_formats[1]);
708
+			} else {
709
+				// set default formats for date and time
710
+				$existing->set_date_format(get_option('date_format'));
711
+				$existing->set_time_format(get_option('time_format'));
712
+			}
713
+			foreach ($props_n_values as $property => $field_value) {
714
+				$existing->set($property, $field_value);
715
+			}
716
+			return $existing;
717
+		}
718
+		return false;
719
+	}
720
+
721
+
722
+	/**
723
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
724
+	 * internally instead of wp set date format options
725
+	 *
726
+	 * @param string $format should be a format recognizable by PHP date() functions.
727
+	 * @since 4.6
728
+	 */
729
+	public function set_date_format($format)
730
+	{
731
+		$this->_dt_frmt = $format;
732
+		// clear cached_properties because they won't be relevant now.
733
+		$this->_clear_cached_properties();
734
+	}
735
+
736
+
737
+	/**
738
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
739
+	 * class internally instead of wp set time format options.
740
+	 *
741
+	 * @param string $format should be a format recognizable by PHP date() functions.
742
+	 * @since 4.6
743
+	 */
744
+	public function set_time_format($format)
745
+	{
746
+		$this->_tm_frmt = $format;
747
+		// clear cached_properties because they won't be relevant now.
748
+		$this->_clear_cached_properties();
749
+	}
750
+
751
+
752
+	/**
753
+	 * This just takes care of clearing out the cached_properties
754
+	 *
755
+	 * @return void
756
+	 */
757
+	protected function _clear_cached_properties()
758
+	{
759
+		$this->_cached_properties = [];
760
+	}
761
+
762
+
763
+	/**
764
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
765
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
766
+	 * you got new information that somehow made you change your mind.
767
+	 *
768
+	 * @param boolean $allow_persist
769
+	 * @return boolean
770
+	 */
771
+	public function set_allow_persist($allow_persist)
772
+	{
773
+		return $this->_allow_persist = $allow_persist;
774
+	}
775
+
776
+
777
+	/**
778
+	 * Gets the field's original value when this object was constructed during this request.
779
+	 * This can be helpful when determining if a model object has changed or not
780
+	 *
781
+	 * @param string $field_name
782
+	 * @return mixed|null
783
+	 * @throws InvalidArgumentException
784
+	 * @throws InvalidInterfaceException
785
+	 * @throws InvalidDataTypeException
786
+	 * @throws EE_Error
787
+	 */
788
+	public function get_original($field_name)
789
+	{
790
+		if (
791
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
792
+			&& $field_settings = $this->_model->field_settings_for($field_name)
793
+		) {
794
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
795
+		}
796
+		return null;
797
+	}
798
+
799
+
800
+	/**
801
+	 * @param EE_Base_Class $obj
802
+	 * @return string
803
+	 */
804
+	public function get_class($obj)
805
+	{
806
+		return get_class($obj);
807
+	}
808
+
809
+
810
+	/**
811
+	 * Set custom select values for model.
812
+	 *
813
+	 * @param array $custom_select_values
814
+	 */
815
+	public function setCustomSelectsValues(array $custom_select_values)
816
+	{
817
+		$this->custom_selection_results = $custom_select_values;
818
+	}
819
+
820
+
821
+	/**
822
+	 * Returns the custom select value for the provided alias if its set.
823
+	 * If not set, returns null.
824
+	 *
825
+	 * @param string $alias
826
+	 * @return string|int|float|null
827
+	 */
828
+	public function getCustomSelect($alias)
829
+	{
830
+		return isset($this->custom_selection_results[ $alias ])
831
+			? $this->custom_selection_results[ $alias ]
832
+			: null;
833
+	}
834
+
835
+
836
+	/**
837
+	 * This sets the field value on the db column if it exists for the given $column_name or
838
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
839
+	 *
840
+	 * @param string $field_name  Must be the exact column name.
841
+	 * @param mixed  $field_value The value to set.
842
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
843
+	 * @throws InvalidArgumentException
844
+	 * @throws InvalidInterfaceException
845
+	 * @throws InvalidDataTypeException
846
+	 * @throws EE_Error
847
+	 * @throws ReflectionException
848
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
849
+	 */
850
+	public function set_field_or_extra_meta($field_name, $field_value)
851
+	{
852
+		if ($this->_model->has_field($field_name)) {
853
+			$this->set($field_name, $field_value);
854
+			return true;
855
+		}
856
+		// ensure this object is saved first so that extra meta can be properly related.
857
+		$this->save();
858
+		return $this->update_extra_meta($field_name, $field_value);
859
+	}
860
+
861
+
862
+	/**
863
+	 *        Saves this object to the database. An array may be supplied to set some values on this
864
+	 * object just before saving.
865
+	 *
866
+	 * @access public
867
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
868
+	 *                                 if provided during the save() method (often client code will change the fields'
869
+	 *                                 values before calling save)
870
+	 * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
871
+	 *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
872
+	 * @throws InvalidInterfaceException
873
+	 * @throws InvalidDataTypeException
874
+	 * @throws EE_Error
875
+	 * @throws InvalidArgumentException
876
+	 * @throws ReflectionException
877
+	 * @throws ReflectionException
878
+	 * @throws ReflectionException
879
+	 */
880
+	public function save($set_cols_n_values = [])
881
+	{
882
+		/**
883
+		 * Filters the fields we're about to save on the model object
884
+		 *
885
+		 * @param array         $set_cols_n_values
886
+		 * @param EE_Base_Class $model_object
887
+		 */
888
+		$set_cols_n_values = (array) apply_filters(
889
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
890
+			$set_cols_n_values,
891
+			$this
892
+		);
893
+		// set attributes as provided in $set_cols_n_values
894
+		foreach ($set_cols_n_values as $column => $value) {
895
+			$this->set($column, $value);
896
+		}
897
+		// no changes ? then don't do anything
898
+		if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
899
+			return 0;
900
+		}
901
+		/**
902
+		 * Saving a model object.
903
+		 * Before we perform a save, this action is fired.
904
+		 *
905
+		 * @param EE_Base_Class $model_object the model object about to be saved.
906
+		 */
907
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
908
+		if (! $this->allow_persist()) {
909
+			return 0;
910
+		}
911
+		// now get current attribute values
912
+		$save_cols_n_values = $this->_fields;
913
+		// if the object already has an ID, update it. Otherwise, insert it
914
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
915
+		// They have been
916
+		$old_assumption_concerning_value_preparation = $this->_model
917
+			->get_assumption_concerning_values_already_prepared_by_model_object();
918
+		$this->_model->assume_values_already_prepared_by_model_object(true);
919
+		// does this model have an autoincrement PK?
920
+		if ($this->_model->has_primary_key_field()) {
921
+			if ($this->_model->get_primary_key_field()->is_auto_increment()) {
922
+				// ok check if it's set, if so: update; if not, insert
923
+				if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
924
+					$results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
925
+				} else {
926
+					unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
927
+					$results = $this->_model->insert($save_cols_n_values);
928
+					if ($results) {
929
+						// if successful, set the primary key
930
+						// but don't use the normal SET method, because it will check if
931
+						// an item with the same ID exists in the mapper & db, then
932
+						// will find it in the db (because we just added it) and THAT object
933
+						// will get added to the mapper before we can add this one!
934
+						// but if we just avoid using the SET method, all that headache can be avoided
935
+						$pk_field_name                   = $this->_model->primary_key_name();
936
+						$this->_fields[ $pk_field_name ] = $results;
937
+						$this->_clear_cached_property($pk_field_name);
938
+						$this->_model->add_to_entity_map($this);
939
+						$this->_update_cached_related_model_objs_fks();
940
+					}
941
+				}
942
+			} else {// PK is NOT auto-increment
943
+				// so check if one like it already exists in the db
944
+				if ($this->_model->exists_by_ID($this->ID())) {
945
+					if (WP_DEBUG && ! $this->in_entity_map()) {
946
+						throw new EE_Error(
947
+							sprintf(
948
+								esc_html__(
949
+									'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',
950
+									'event_espresso'
951
+								),
952
+								get_class($this),
953
+								get_class($this->_model) . '::instance()->add_to_entity_map()',
954
+								get_class($this->_model) . '::instance()->get_one_by_ID()',
955
+								'<br />'
956
+							)
957
+						);
958
+					}
959
+					$results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
960
+				} else {
961
+					$results = $this->_model->insert($save_cols_n_values);
962
+					$this->_update_cached_related_model_objs_fks();
963
+				}
964
+			}
965
+		} else {// there is NO primary key
966
+			$already_in_db = false;
967
+			foreach ($this->_model->unique_indexes() as $index) {
968
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
969
+				if ($this->_model->exists([$uniqueness_where_params])) {
970
+					$already_in_db = true;
971
+				}
972
+			}
973
+			if ($already_in_db) {
974
+				$combined_pk_fields_n_values = array_intersect_key(
975
+					$save_cols_n_values,
976
+					$this->_model->get_combined_primary_key_fields()
977
+				);
978
+				$results                     = $this->_model->update(
979
+					$save_cols_n_values,
980
+					$combined_pk_fields_n_values
981
+				);
982
+			} else {
983
+				$results = $this->_model->insert($save_cols_n_values);
984
+			}
985
+		}
986
+		// restore the old assumption about values being prepared by the model object
987
+		$this->_model->assume_values_already_prepared_by_model_object(
988
+			$old_assumption_concerning_value_preparation
989
+		);
990
+		/**
991
+		 * After saving the model object this action is called
992
+		 *
993
+		 * @param EE_Base_Class $model_object which was just saved
994
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
995
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
996
+		 */
997
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
998
+		$this->_has_changes = false;
999
+		return $results;
1000
+	}
1001
+
1002
+
1003
+	/**
1004
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
1005
+	 * A $previous_value can be specified in case there are many meta rows with the same key
1006
+	 *
1007
+	 * @param string $meta_key
1008
+	 * @param mixed  $meta_value
1009
+	 * @param mixed  $previous_value
1010
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
1011
+	 *                  NOTE: if the values haven't changed, returns 0
1012
+	 * @throws InvalidArgumentException
1013
+	 * @throws InvalidInterfaceException
1014
+	 * @throws InvalidDataTypeException
1015
+	 * @throws EE_Error
1016
+	 * @throws ReflectionException
1017
+	 */
1018
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
1019
+	{
1020
+		$query_params = [
1021
+			[
1022
+				'EXM_key'  => $meta_key,
1023
+				'OBJ_ID'   => $this->ID(),
1024
+				'EXM_type' => $this->_model->get_this_model_name(),
1025
+			],
1026
+		];
1027
+		if ($previous_value !== null) {
1028
+			$query_params[0]['EXM_value'] = $meta_value;
1029
+		}
1030
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
1031
+		if (! $existing_rows_like_that) {
1032
+			return $this->add_extra_meta($meta_key, $meta_value);
1033
+		}
1034
+		foreach ($existing_rows_like_that as $existing_row) {
1035
+			$existing_row->save(['EXM_value' => $meta_value]);
1036
+		}
1037
+		return count($existing_rows_like_that);
1038
+	}
1039
+
1040
+
1041
+	/**
1042
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
1043
+	 *
1044
+	 * @return boolean
1045
+	 */
1046
+	public function allow_persist()
1047
+	{
1048
+		return $this->_allow_persist;
1049
+	}
1050
+
1051
+
1052
+	/**
1053
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1054
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1055
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1056
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1057
+	 * 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
1058
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1059
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1060
+	 *
1061
+	 * @return void
1062
+	 * @throws ReflectionException
1063
+	 * @throws InvalidArgumentException
1064
+	 * @throws InvalidInterfaceException
1065
+	 * @throws InvalidDataTypeException
1066
+	 * @throws EE_Error
1067
+	 */
1068
+	protected function _update_cached_related_model_objs_fks()
1069
+	{
1070
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1071
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1072
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1073
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1074
+						$this->_model->get_this_model_name()
1075
+					);
1076
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1077
+					if ($related_model_obj_in_cache->ID()) {
1078
+						$related_model_obj_in_cache->save();
1079
+					}
1080
+				}
1081
+			}
1082
+		}
1083
+	}
1084
+
1085
+
1086
+	/**
1087
+	 * in_entity_map
1088
+	 * Checks if this model object has been proven to already be in the entity map
1089
+	 *
1090
+	 * @return boolean
1091
+	 * @throws InvalidArgumentException
1092
+	 * @throws InvalidInterfaceException
1093
+	 * @throws InvalidDataTypeException
1094
+	 * @throws EE_Error
1095
+	 */
1096
+	public function in_entity_map()
1097
+	{
1098
+		// well, if we looked, did we find it in the entity map?
1099
+		return $this->ID() && $this->_model->get_from_entity_map($this->ID()) === $this;
1100
+	}
1101
+
1102
+
1103
+	/**
1104
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
1105
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
1106
+	 * extra meta row was entered, false if not
1107
+	 *
1108
+	 * @param string  $meta_key
1109
+	 * @param mixed   $meta_value
1110
+	 * @param boolean $unique
1111
+	 * @return boolean
1112
+	 * @throws InvalidArgumentException
1113
+	 * @throws InvalidInterfaceException
1114
+	 * @throws InvalidDataTypeException
1115
+	 * @throws EE_Error
1116
+	 * @throws ReflectionException
1117
+	 * @throws ReflectionException
1118
+	 */
1119
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
1120
+	{
1121
+		if ($unique) {
1122
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
1123
+				[
1124
+					[
1125
+						'EXM_key'  => $meta_key,
1126
+						'OBJ_ID'   => $this->ID(),
1127
+						'EXM_type' => $this->_model->get_this_model_name(),
1128
+					],
1129
+				]
1130
+			);
1131
+			if ($existing_extra_meta) {
1132
+				return false;
1133
+			}
1134
+		}
1135
+		$new_extra_meta = EE_Extra_Meta::new_instance(
1136
+			[
1137
+				'EXM_key'   => $meta_key,
1138
+				'EXM_value' => $meta_value,
1139
+				'OBJ_ID'    => $this->ID(),
1140
+				'EXM_type'  => $this->_model->get_this_model_name(),
1141
+			]
1142
+		);
1143
+		$new_extra_meta->save();
1144
+		return true;
1145
+	}
1146
+
1147
+
1148
+	/**
1149
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1150
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1151
+	 *
1152
+	 * @param string $relationName
1153
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
1154
+	 * @throws InvalidArgumentException
1155
+	 * @throws InvalidInterfaceException
1156
+	 * @throws InvalidDataTypeException
1157
+	 * @throws EE_Error
1158
+	 * @throws ReflectionException
1159
+	 */
1160
+	public function get_all_from_cache($relationName)
1161
+	{
1162
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
1163
+		// if the result is not an array, but exists, make it an array
1164
+		$objects = is_array($objects) ? $objects : [$objects];
1165
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
1166
+		// basically, if this model object was stored in the session, and these cached model objects
1167
+		// already have IDs, let's make sure they're in their model's entity mapper
1168
+		// otherwise we will have duplicates next time we call
1169
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
1170
+		$related_model = EE_Registry::instance()->load_model($relationName);
1171
+		foreach ($objects as $model_object) {
1172
+			if ($related_model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
1173
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
1174
+				if ($model_object->ID()) {
1175
+					$related_model->add_to_entity_map($model_object);
1176
+				}
1177
+			} else {
1178
+				throw new EE_Error(
1179
+					sprintf(
1180
+						esc_html__(
1181
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
1182
+							'event_espresso'
1183
+						),
1184
+						$relationName,
1185
+						gettype($model_object)
1186
+					)
1187
+				);
1188
+			}
1189
+		}
1190
+		return $objects;
1191
+	}
1192
+
1193
+
1194
+	/**
1195
+	 * This retrieves the value of the db column set on this class or if that's not present
1196
+	 * it will attempt to retrieve from extra_meta if found.
1197
+	 * Example Usage:
1198
+	 * Via EE_Message child class:
1199
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
1200
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
1201
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
1202
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
1203
+	 * value for those extra fields dynamically via the EE_message object.
1204
+	 *
1205
+	 * @param string $field_name expecting the fully qualified field name.
1206
+	 * @return mixed|null  value for the field if found.  null if not found.
1207
+	 * @throws ReflectionException
1208
+	 * @throws InvalidArgumentException
1209
+	 * @throws InvalidInterfaceException
1210
+	 * @throws InvalidDataTypeException
1211
+	 * @throws EE_Error
1212
+	 */
1213
+	public function get_field_or_extra_meta($field_name)
1214
+	{
1215
+		if ($this->_model->has_field($field_name)) {
1216
+			$column_value = $this->get($field_name);
1217
+		} else {
1218
+			// This isn't a column in the main table, let's see if it is in the extra meta.
1219
+			$column_value = $this->get_extra_meta($field_name, true);
1220
+		}
1221
+		return $column_value;
1222
+	}
1223
+
1224
+
1225
+	/**
1226
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
1227
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
1228
+	 * You can specify $default is case you haven't found the extra meta
1229
+	 *
1230
+	 * @param string  $meta_key
1231
+	 * @param boolean $single
1232
+	 * @param mixed   $default if we don't find anything, what should we return?
1233
+	 * @return mixed single value if $single; array if ! $single
1234
+	 * @throws ReflectionException
1235
+	 * @throws InvalidArgumentException
1236
+	 * @throws InvalidInterfaceException
1237
+	 * @throws InvalidDataTypeException
1238
+	 * @throws EE_Error
1239
+	 */
1240
+	public function get_extra_meta($meta_key, $single = false, $default = null)
1241
+	{
1242
+		if ($single) {
1243
+			$result = $this->get_first_related(
1244
+				'Extra_Meta',
1245
+				[['EXM_key' => $meta_key]]
1246
+			);
1247
+			if ($result instanceof EE_Extra_Meta) {
1248
+				return $result->value();
1249
+			}
1250
+		} else {
1251
+			$results = $this->get_many_related(
1252
+				'Extra_Meta',
1253
+				[['EXM_key' => $meta_key]]
1254
+			);
1255
+			if ($results) {
1256
+				$values = [];
1257
+				foreach ($results as $result) {
1258
+					if ($result instanceof EE_Extra_Meta) {
1259
+						$values[ $result->ID() ] = $result->value();
1260
+					}
1261
+				}
1262
+				return $values;
1263
+			}
1264
+		}
1265
+		// if nothing discovered yet return default.
1266
+		return apply_filters(
1267
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
1268
+			$default,
1269
+			$meta_key,
1270
+			$single,
1271
+			$this
1272
+		);
1273
+	}
1274
+
1275
+
1276
+	/**
1277
+	 * Gets the first (ie, one) related model object of the specified type.
1278
+	 *
1279
+	 * @param string $relationName key in the model's _model_relations array
1280
+	 * @param array  $query_params @see
1281
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1282
+	 * @return EE_Base_Class (not an array, a single object)
1283
+	 * @throws ReflectionException
1284
+	 * @throws InvalidArgumentException
1285
+	 * @throws InvalidInterfaceException
1286
+	 * @throws InvalidDataTypeException
1287
+	 * @throws EE_Error
1288
+	 */
1289
+	public function get_first_related($relationName, $query_params = [])
1290
+	{
1291
+		$model_relation = $this->_model->related_settings_for($relationName);
1292
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
1293
+			// if they've provided some query parameters, don't bother trying to cache the result
1294
+			// also make sure we're not caching the result of get_first_related
1295
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
1296
+			if (
1297
+				$query_params
1298
+				|| ! $model_relation instanceof EE_Belongs_To_Relation
1299
+			) {
1300
+				$related_model_object = $this->_model->get_first_related(
1301
+					$this,
1302
+					$relationName,
1303
+					$query_params
1304
+				);
1305
+			} else {
1306
+				// first, check if we've already cached the result of this query
1307
+				$cached_result = $this->get_one_from_cache($relationName);
1308
+				if (! $cached_result) {
1309
+					$related_model_object = $this->_model->get_first_related(
1310
+						$this,
1311
+						$relationName,
1312
+						$query_params
1313
+					);
1314
+					$this->cache($relationName, $related_model_object);
1315
+				} else {
1316
+					$related_model_object = $cached_result;
1317
+				}
1318
+			}
1319
+		} else {
1320
+			$related_model_object = null;
1321
+			// this doesn't exist in the Db,
1322
+			// but maybe the relation is of type belongs to, and so the related thing might
1323
+			if ($model_relation instanceof EE_Belongs_To_Relation) {
1324
+				$related_model_object = $this->_model->get_first_related(
1325
+					$this,
1326
+					$relationName,
1327
+					$query_params
1328
+				);
1329
+			}
1330
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
1331
+			// just get what's cached on this object
1332
+			if (! $related_model_object) {
1333
+				$related_model_object = $this->get_one_from_cache($relationName);
1334
+			}
1335
+		}
1336
+		return $related_model_object;
1337
+	}
1338
+
1339
+
1340
+	/**
1341
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
1342
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
1343
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
1344
+	 * because we want to get even deleted items etc.
1345
+	 *
1346
+	 * @param string $relationName key in the model's _model_relations array
1347
+	 * @param array  $query_params @see
1348
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1349
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
1350
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
1351
+	 *                             results if you want IDs
1352
+	 * @throws ReflectionException
1353
+	 * @throws InvalidArgumentException
1354
+	 * @throws InvalidInterfaceException
1355
+	 * @throws InvalidDataTypeException
1356
+	 * @throws EE_Error
1357
+	 */
1358
+	public function get_many_related($relationName, $query_params = [])
1359
+	{
1360
+		if ($this->ID()) {
1361
+			// this exists in the DB, so get the related things from either the cache or the DB
1362
+			// if there are query parameters, forget about caching the related model objects.
1363
+			if ($query_params) {
1364
+				$related_model_objects = $this->_model->get_all_related(
1365
+					$this,
1366
+					$relationName,
1367
+					$query_params
1368
+				);
1369
+			} else {
1370
+				// did we already cache the result of this query?
1371
+				$cached_results = $this->get_all_from_cache($relationName);
1372
+				if (! $cached_results) {
1373
+					$related_model_objects = $this->_model->get_all_related(
1374
+						$this,
1375
+						$relationName,
1376
+						$query_params
1377
+					);
1378
+					// if no query parameters were passed, then we got all the related model objects
1379
+					// for that relation. We can cache them then.
1380
+					foreach ($related_model_objects as $related_model_object) {
1381
+						$this->cache($relationName, $related_model_object);
1382
+					}
1383
+				} else {
1384
+					$related_model_objects = $cached_results;
1385
+				}
1386
+			}
1387
+		} else {
1388
+			// this doesn't exist in the DB, so just get the related things from the cache
1389
+			$related_model_objects = $this->get_all_from_cache($relationName);
1390
+		}
1391
+		return $related_model_objects;
1392
+	}
1393
+
1394
+
1395
+	/**
1396
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
1397
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
1398
+	 *
1399
+	 * @param string $relationName
1400
+	 * @return EE_Base_Class
1401
+	 */
1402
+	public function get_one_from_cache($relationName)
1403
+	{
1404
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
1405
+			? $this->_model_relations[ $relationName ]
1406
+			: null;
1407
+		if (is_array($cached_array_or_object)) {
1408
+			return array_shift($cached_array_or_object);
1409
+		}
1410
+		return $cached_array_or_object;
1411
+	}
1412
+
1413
+
1414
+	/**
1415
+	 * cache
1416
+	 * stores the passed model object on the current model object.
1417
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
1418
+	 *
1419
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
1420
+	 *                                       'Registration' associated with this model object
1421
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
1422
+	 *                                       that could be a payment or a registration)
1423
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
1424
+	 *                                       items which will be stored in an array on this object
1425
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
1426
+	 *                                       related thing, no array)
1427
+	 * @throws InvalidArgumentException
1428
+	 * @throws InvalidInterfaceException
1429
+	 * @throws InvalidDataTypeException
1430
+	 * @throws EE_Error
1431
+	 */
1432
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
1433
+	{
1434
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
1435
+		if (! $object_to_cache instanceof EE_Base_Class) {
1436
+			return false;
1437
+		}
1438
+		// also get "how" the object is related, or throw an error
1439
+		if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1440
+			throw new EE_Error(
1441
+				sprintf(
1442
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
1443
+					$relationName,
1444
+					get_class($this)
1445
+				)
1446
+			);
1447
+		}
1448
+		// how many things are related ?
1449
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1450
+			// if it's a "belongs to" relationship, then there's only one related model object
1451
+			// eg, if this is a registration, there's only 1 attendee for it
1452
+			// so for these model objects just set it to be cached
1453
+			$this->_model_relations[ $relationName ] = $object_to_cache;
1454
+			$return                                  = true;
1455
+		} else {
1456
+			// otherwise, this is the "many" side of a one to many relationship,
1457
+			// so we'll add the object to the array of related objects for that type.
1458
+			// eg: if this is an event, there are many registrations for that event,
1459
+			// so we cache the registrations in an array
1460
+			if (! is_array($this->_model_relations[ $relationName ])) {
1461
+				// if for some reason, the cached item is a model object,
1462
+				// then stick that in the array, otherwise start with an empty array
1463
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
1464
+														   instanceof
1465
+														   EE_Base_Class
1466
+					? [$this->_model_relations[ $relationName ]] : [];
1467
+			}
1468
+			// first check for a cache_id which is normally empty
1469
+			if (! empty($cache_id)) {
1470
+				// if the cache_id exists, then it means we are purposely trying to cache this
1471
+				// with a known key that can then be used to retrieve the object later on
1472
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
1473
+				$return                                               = $cache_id;
1474
+			} elseif ($object_to_cache->ID()) {
1475
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
1476
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
1477
+				$return                                                            = $object_to_cache->ID();
1478
+			} else {
1479
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
1480
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
1481
+				// move the internal pointer to the end of the array
1482
+				end($this->_model_relations[ $relationName ]);
1483
+				// and grab the key so that we can return it
1484
+				$return = key($this->_model_relations[ $relationName ]);
1485
+			}
1486
+		}
1487
+		return $return;
1488
+	}
1489
+
1490
+
1491
+	/**
1492
+	 * This just returns whatever is set for the current timezone.
1493
+	 *
1494
+	 * @access public
1495
+	 * @return string timezone string
1496
+	 */
1497
+	public function get_timezone()
1498
+	{
1499
+		return $this->_timezone;
1500
+	}
1501
+
1502
+
1503
+	/**
1504
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
1505
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
1506
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
1507
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
1508
+	 *
1509
+	 * @access public
1510
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
1511
+	 * @return void
1512
+	 * @throws InvalidArgumentException
1513
+	 * @throws InvalidInterfaceException
1514
+	 * @throws InvalidDataTypeException
1515
+	 */
1516
+	public function set_timezone($timezone = '')
1517
+	{
1518
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
1519
+		// make sure we clear all cached properties because they won't be relevant now
1520
+		$this->_clear_cached_properties();
1521
+		// make sure we update field settings and the date for all EE_Datetime_Fields
1522
+		$model_fields = $this->_model->field_settings();
1523
+		foreach ($model_fields as $field_name => $field_obj) {
1524
+			if ($field_obj instanceof EE_Datetime_Field) {
1525
+				$field_obj->set_timezone($this->_timezone);
1526
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
1527
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
1528
+				}
1529
+			}
1530
+		}
1531
+	}
1532
+
1533
+
1534
+	/**
1535
+	 * This returns the current internal set format for the date and time formats.
1536
+	 *
1537
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
1538
+	 *                             where the first value is the date format and the second value is the time format.
1539
+	 * @return mixed string|array
1540
+	 */
1541
+	public function get_format($full = true)
1542
+	{
1543
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1544
+	}
1545
+
1546
+
1547
+	/**
1548
+	 * update_cache_after_object_save
1549
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
1550
+	 * obtained after being saved to the db
1551
+	 *
1552
+	 * @param string        $relationName       - the type of object that is cached
1553
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
1554
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
1555
+	 * @return boolean TRUE on success, FALSE on fail
1556
+	 * @throws InvalidArgumentException
1557
+	 * @throws InvalidInterfaceException
1558
+	 * @throws InvalidDataTypeException
1559
+	 * @throws EE_Error
1560
+	 */
1561
+	public function update_cache_after_object_save(
1562
+		$relationName,
1563
+		EE_Base_Class $newly_saved_object,
1564
+		$current_cache_id = ''
1565
+	) {
1566
+		// verify that incoming object is of the correct type
1567
+		$obj_class = 'EE_' . $relationName;
1568
+		if ($newly_saved_object instanceof $obj_class) {
1569
+			/* @type EE_Base_Class $newly_saved_object */
1570
+			// now get the type of relation
1571
+			$relationship_to_model = $this->_model->related_settings_for($relationName);
1572
+			// if this is a 1:1 relationship
1573
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1574
+				// then just replace the cached object with the newly saved object
1575
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
1576
+				return true;
1577
+				// or if it's some kind of sordid feral polyamorous relationship...
1578
+			}
1579
+			if (
1580
+				is_array($this->_model_relations[ $relationName ])
1581
+				&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
1582
+			) {
1583
+				// then remove the current cached item
1584
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
1585
+				// and cache the newly saved object using it's new ID
1586
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
1587
+				return true;
1588
+			}
1589
+		}
1590
+		return false;
1591
+	}
1592
+
1593
+
1594
+	/**
1595
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1596
+	 * matching the given query conditions.
1597
+	 *
1598
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1599
+	 * @param int   $limit              How many objects to return.
1600
+	 * @param array $query_params       Any additional conditions on the query.
1601
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1602
+	 *                                  you can indicate just the columns you want returned
1603
+	 * @return array|EE_Base_Class[]
1604
+	 * @throws ReflectionException
1605
+	 * @throws InvalidArgumentException
1606
+	 * @throws InvalidInterfaceException
1607
+	 * @throws InvalidDataTypeException
1608
+	 * @throws EE_Error
1609
+	 */
1610
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1611
+	{
1612
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1613
+			? $this->_model->get_primary_key_field()->get_name()
1614
+			: $field_to_order_by;
1615
+		$current_value = ! empty($field) ? $this->get($field) : null;
1616
+		if (empty($field) || empty($current_value)) {
1617
+			return [];
1618
+		}
1619
+		return $this->_model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1620
+	}
1621
+
1622
+
1623
+	/**
1624
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1625
+	 * matching the given query conditions.
1626
+	 *
1627
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1628
+	 * @param int   $limit              How many objects to return.
1629
+	 * @param array $query_params       Any additional conditions on the query.
1630
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1631
+	 *                                  you can indicate just the columns you want returned
1632
+	 * @return array|EE_Base_Class[]
1633
+	 * @throws ReflectionException
1634
+	 * @throws InvalidArgumentException
1635
+	 * @throws InvalidInterfaceException
1636
+	 * @throws InvalidDataTypeException
1637
+	 * @throws EE_Error
1638
+	 */
1639
+	public function previous_x(
1640
+		$field_to_order_by = null,
1641
+		$limit = 1,
1642
+		$query_params = [],
1643
+		$columns_to_select = null
1644
+	) {
1645
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1646
+			? $this->_model->get_primary_key_field()->get_name()
1647
+			: $field_to_order_by;
1648
+		$current_value = ! empty($field) ? $this->get($field) : null;
1649
+		if (empty($field) || empty($current_value)) {
1650
+			return [];
1651
+		}
1652
+		return $this->_model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1653
+	}
1654
+
1655
+
1656
+	/**
1657
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1658
+	 * matching the given query conditions.
1659
+	 *
1660
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1661
+	 * @param array $query_params       Any additional conditions on the query.
1662
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1663
+	 *                                  you can indicate just the columns you want returned
1664
+	 * @return array|EE_Base_Class
1665
+	 * @throws ReflectionException
1666
+	 * @throws InvalidArgumentException
1667
+	 * @throws InvalidInterfaceException
1668
+	 * @throws InvalidDataTypeException
1669
+	 * @throws EE_Error
1670
+	 */
1671
+	public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1672
+	{
1673
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1674
+			? $this->_model->get_primary_key_field()->get_name()
1675
+			: $field_to_order_by;
1676
+		$current_value = ! empty($field) ? $this->get($field) : null;
1677
+		if (empty($field) || empty($current_value)) {
1678
+			return [];
1679
+		}
1680
+		return $this->_model->next($current_value, $field, $query_params, $columns_to_select);
1681
+	}
1682
+
1683
+
1684
+	/**
1685
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1686
+	 * matching the given query conditions.
1687
+	 *
1688
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1689
+	 * @param array $query_params       Any additional conditions on the query.
1690
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1691
+	 *                                  you can indicate just the column you want returned
1692
+	 * @return array|EE_Base_Class
1693
+	 * @throws ReflectionException
1694
+	 * @throws InvalidArgumentException
1695
+	 * @throws InvalidInterfaceException
1696
+	 * @throws InvalidDataTypeException
1697
+	 * @throws EE_Error
1698
+	 */
1699
+	public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1700
+	{
1701
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1702
+			? $this->_model->get_primary_key_field()->get_name()
1703
+			: $field_to_order_by;
1704
+		$current_value = ! empty($field) ? $this->get($field) : null;
1705
+		if (empty($field) || empty($current_value)) {
1706
+			return [];
1707
+		}
1708
+		return $this->_model->previous($current_value, $field, $query_params, $columns_to_select);
1709
+	}
1710
+
1711
+
1712
+	/**
1713
+	 * This is used to return the internal DateTime object used for a field that is a
1714
+	 * EE_Datetime_Field.
1715
+	 *
1716
+	 * @param string $field_name               The field name retrieving the DateTime object.
1717
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1718
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1719
+	 *                                         EE_Datetime_Field and but the field value is null, then
1720
+	 *                                         just null is returned (because that indicates that likely
1721
+	 *                                         this field is nullable).
1722
+	 * @throws InvalidArgumentException
1723
+	 * @throws InvalidDataTypeException
1724
+	 * @throws InvalidInterfaceException
1725
+	 */
1726
+	public function get_DateTime_object($field_name)
1727
+	{
1728
+		$field_settings = $this->_model->field_settings_for($field_name);
1729
+		if (! $field_settings instanceof EE_Datetime_Field) {
1730
+			EE_Error::add_error(
1731
+				sprintf(
1732
+					esc_html__(
1733
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1734
+						'event_espresso'
1735
+					),
1736
+					$field_name
1737
+				),
1738
+				__FILE__,
1739
+				__FUNCTION__,
1740
+				__LINE__
1741
+			);
1742
+			return false;
1743
+		}
1744
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1745
+			? clone $this->_fields[ $field_name ]
1746
+			: null;
1747
+	}
1748
+
1749
+
1750
+
1751
+
1752
+	/**
1753
+	 * NOTE ABOUT BELOW:
1754
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1755
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1756
+	 * you want to set both date and time at the same time, you can just use the models default set($field_name,$value)
1757
+	 * method and make sure you send the entire datetime value for setting.
1758
+	 */
1759
+
1760
+
1761
+	/**
1762
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1763
+	 * can be easily used as the value of form input.
1764
+	 *
1765
+	 * @param string $field_name
1766
+	 * @return void
1767
+	 * @throws InvalidArgumentException
1768
+	 * @throws InvalidInterfaceException
1769
+	 * @throws InvalidDataTypeException
1770
+	 * @throws EE_Error
1771
+	 */
1772
+	public function f($field_name)
1773
+	{
1774
+		$this->e($field_name, 'form_input');
1775
+	}
1776
+
1777
+
1778
+	/**
1779
+	 * To be used in template to immediately echo out the value, and format it for output.
1780
+	 * Eg, should call stripslashes and whatnot before echoing
1781
+	 *
1782
+	 * @param string $field_name      the name of the field as it appears in the DB
1783
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1784
+	 *                                (in cases where the same property may be used for different outputs
1785
+	 *                                - i.e. datetime, money etc.)
1786
+	 * @return void
1787
+	 * @throws InvalidArgumentException
1788
+	 * @throws InvalidInterfaceException
1789
+	 * @throws InvalidDataTypeException
1790
+	 * @throws EE_Error
1791
+	 */
1792
+	public function e($field_name, $extra_cache_ref = null)
1793
+	{
1794
+		echo $this->get_pretty($field_name, $extra_cache_ref);
1795
+	}
1796
+
1797
+
1798
+	/**
1799
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1800
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1801
+	 * to see what options are available.
1802
+	 *
1803
+	 * @param string $field_name
1804
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1805
+	 *                                (in cases where the same property may be used for different outputs
1806
+	 *                                - i.e. datetime, money etc.)
1807
+	 * @return mixed
1808
+	 * @throws InvalidArgumentException
1809
+	 * @throws InvalidInterfaceException
1810
+	 * @throws InvalidDataTypeException
1811
+	 * @throws EE_Error
1812
+	 */
1813
+	public function get_pretty($field_name, $extra_cache_ref = null)
1814
+	{
1815
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1816
+	}
1817
+
1818
+
1819
+	/**
1820
+	 * Same as `f()` but just returns the value instead of echoing it
1821
+	 *
1822
+	 * @param string $field_name
1823
+	 * @return string
1824
+	 * @throws InvalidArgumentException
1825
+	 * @throws InvalidInterfaceException
1826
+	 * @throws InvalidDataTypeException
1827
+	 * @throws EE_Error
1828
+	 */
1829
+	public function get_f($field_name)
1830
+	{
1831
+		return (string) $this->get_pretty($field_name, 'form_input');
1832
+	}
1833
+
1834
+
1835
+	/**
1836
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1837
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1838
+	 * other echoes the pretty value for dtt)
1839
+	 *
1840
+	 * @param string $field_name name of model object datetime field holding the value
1841
+	 * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1842
+	 * @return string            datetime value formatted
1843
+	 * @throws InvalidArgumentException
1844
+	 * @throws InvalidInterfaceException
1845
+	 * @throws InvalidDataTypeException
1846
+	 * @throws EE_Error
1847
+	 */
1848
+	public function get_date($field_name, $format = '')
1849
+	{
1850
+		return $this->_get_datetime($field_name, $format, null, 'D');
1851
+	}
1852
+
1853
+
1854
+	/**
1855
+	 * This simply returns the datetime for the given field name
1856
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1857
+	 * (and the equivalent e_date, e_time, e_datetime).
1858
+	 *
1859
+	 * @access   protected
1860
+	 * @param string  $field_name    Field on the instantiated EE_Base_Class child object
1861
+	 * @param string  $date_format   valid datetime format used for date
1862
+	 *                               (if '' then we just use the default on the field,
1863
+	 *                               if NULL we use the last-used format)
1864
+	 * @param string  $time_format   Same as above except this is for time format
1865
+	 * @param string  $date_or_time  if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1866
+	 * @param boolean $echo          Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1867
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1868
+	 *                               if field is not a valid dtt field, or void if echoing
1869
+	 * @throws InvalidArgumentException
1870
+	 * @throws InvalidInterfaceException
1871
+	 * @throws InvalidDataTypeException
1872
+	 * @throws EE_Error
1873
+	 */
1874
+	protected function _get_datetime(
1875
+		$field_name,
1876
+		$date_format = '',
1877
+		$time_format = '',
1878
+		$date_or_time = '',
1879
+		$echo = false
1880
+	) {
1881
+		// clear cached property
1882
+		$this->_clear_cached_property($field_name);
1883
+		// reset format properties because they are used in get()
1884
+		$this->_dt_frmt = $date_format !== '' ? $date_format : $this->_dt_frmt;
1885
+		$this->_tm_frmt = $time_format !== '' ? $time_format : $this->_tm_frmt;
1886
+		if ($echo) {
1887
+			$this->e($field_name, $date_or_time);
1888
+			return '';
1889
+		}
1890
+		return $this->get($field_name, $date_or_time);
1891
+	}
1892
+
1893
+
1894
+	/**
1895
+	 * @param        $field_name
1896
+	 * @param string $format
1897
+	 * @throws InvalidArgumentException
1898
+	 * @throws InvalidInterfaceException
1899
+	 * @throws InvalidDataTypeException
1900
+	 * @throws EE_Error
1901
+	 */
1902
+	public function e_date($field_name, $format = '')
1903
+	{
1904
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1905
+	}
1906
+
1907
+
1908
+	/**
1909
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1910
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1911
+	 * other echoes the pretty value for dtt)
1912
+	 *
1913
+	 * @param string $field_name name of model object datetime field holding the value
1914
+	 * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1915
+	 * @return string             datetime value formatted
1916
+	 * @throws InvalidArgumentException
1917
+	 * @throws InvalidInterfaceException
1918
+	 * @throws InvalidDataTypeException
1919
+	 * @throws EE_Error
1920
+	 */
1921
+	public function get_time($field_name, $format = '')
1922
+	{
1923
+		return $this->_get_datetime($field_name, null, $format, 'T');
1924
+	}
1925
+
1926
+
1927
+	/**
1928
+	 * @param        $field_name
1929
+	 * @param string $format
1930
+	 * @throws InvalidArgumentException
1931
+	 * @throws InvalidInterfaceException
1932
+	 * @throws InvalidDataTypeException
1933
+	 * @throws EE_Error
1934
+	 */
1935
+	public function e_time($field_name, $format = '')
1936
+	{
1937
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1938
+	}
1939
+
1940
+
1941
+	/**
1942
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1943
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1944
+	 * other echoes the pretty value for dtt)
1945
+	 *
1946
+	 * @param string $field_name  name of model object datetime field holding the value
1947
+	 * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1948
+	 * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1949
+	 * @return string             datetime value formatted
1950
+	 * @throws InvalidArgumentException
1951
+	 * @throws InvalidInterfaceException
1952
+	 * @throws InvalidDataTypeException
1953
+	 * @throws EE_Error
1954
+	 */
1955
+	public function get_datetime($field_name, $date_format = '', $time_format = '')
1956
+	{
1957
+		return $this->_get_datetime($field_name, $date_format, $time_format);
1958
+	}
1959
+
1960
+
1961
+	/**
1962
+	 * @param string $field_name
1963
+	 * @param string $date_format
1964
+	 * @param string $time_format
1965
+	 * @throws InvalidArgumentException
1966
+	 * @throws InvalidInterfaceException
1967
+	 * @throws InvalidDataTypeException
1968
+	 * @throws EE_Error
1969
+	 */
1970
+	public function e_datetime($field_name, $date_format = '', $time_format = '')
1971
+	{
1972
+		$this->_get_datetime($field_name, $date_format, $time_format, null, true);
1973
+	}
1974
+
1975
+
1976
+	/**
1977
+	 * Get the i8ln value for a date using the WordPress @param string $field_name The EE_Datetime_Field reference for
1978
+	 *                           the date being retrieved.
1979
+	 *
1980
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1981
+	 *                           on the object will be used.
1982
+	 * @return string Date and time string in set locale or false if no field exists for the given
1983
+	 * @throws InvalidArgumentException
1984
+	 * @throws InvalidInterfaceException
1985
+	 * @throws InvalidDataTypeException
1986
+	 * @throws EE_Error
1987
+	 *                           field name.
1988
+	 * @see date_i18n function.
1989
+	 */
1990
+	public function get_i18n_datetime($field_name, $format = '')
1991
+	{
1992
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1993
+		return date_i18n(
1994
+			$format,
1995
+			EEH_DTT_Helper::get_timestamp_with_offset(
1996
+				$this->get_raw($field_name),
1997
+				$this->_timezone
1998
+			)
1999
+		);
2000
+	}
2001
+
2002
+
2003
+	/**
2004
+	 * This method simply returns the RAW unprocessed value for the given property in this class
2005
+	 *
2006
+	 * @param string $field_name A valid field name
2007
+	 * @return mixed              Whatever the raw value stored on the property is.
2008
+	 * @throws InvalidArgumentException
2009
+	 * @throws InvalidInterfaceException
2010
+	 * @throws InvalidDataTypeException
2011
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
2012
+	 */
2013
+	public function get_raw($field_name)
2014
+	{
2015
+		$field_settings = $this->_model->field_settings_for($field_name);
2016
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
2017
+			? $this->_fields[ $field_name ]->format('U')
2018
+			: $this->_fields[ $field_name ];
2019
+	}
2020
+
2021
+
2022
+	/**
2023
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
2024
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
2025
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
2026
+	 * that could lead to some unexpected results!
2027
+	 *
2028
+	 * @access public
2029
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
2030
+	 *                                         value being returned.
2031
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
2032
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
2033
+	 * @param string $prepend                  You can include something to prepend on the timestamp
2034
+	 * @param string $append                   You can include something to append on the timestamp
2035
+	 * @return string timestamp
2036
+	 * @throws InvalidArgumentException
2037
+	 * @throws InvalidInterfaceException
2038
+	 * @throws InvalidDataTypeException
2039
+	 * @throws EE_Error
2040
+	 */
2041
+	public function display_in_my_timezone(
2042
+		$field_name,
2043
+		$callback = 'get_datetime',
2044
+		$args = null,
2045
+		$prepend = '',
2046
+		$append = ''
2047
+	) {
2048
+		$timezone = EEH_DTT_Helper::get_timezone();
2049
+		if ($timezone === $this->_timezone) {
2050
+			return '';
2051
+		}
2052
+		$original_timezone = $this->_timezone;
2053
+		$this->set_timezone($timezone);
2054
+		$fn   = (array) $field_name;
2055
+		$args = array_merge($fn, (array) $args);
2056
+		if (! method_exists($this, $callback)) {
2057
+			throw new EE_Error(
2058
+				sprintf(
2059
+					esc_html__(
2060
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
2061
+						'event_espresso'
2062
+					),
2063
+					$callback
2064
+				)
2065
+			);
2066
+		}
2067
+		$args   = (array) $args;
2068
+		$return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
2069
+		$this->set_timezone($original_timezone);
2070
+		return $return;
2071
+	}
2072
+
2073
+
2074
+	/**
2075
+	 * Deletes this model object.
2076
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
2077
+	 * override
2078
+	 * `EE_Base_Class::_delete` NOT this class.
2079
+	 *
2080
+	 * @return boolean | int
2081
+	 * @throws ReflectionException
2082
+	 * @throws InvalidArgumentException
2083
+	 * @throws InvalidInterfaceException
2084
+	 * @throws InvalidDataTypeException
2085
+	 * @throws EE_Error
2086
+	 */
2087
+	public function delete()
2088
+	{
2089
+		/**
2090
+		 * Called just before the `EE_Base_Class::_delete` method call.
2091
+		 * Note:
2092
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2093
+		 * should be aware that `_delete` may not always result in a permanent delete.
2094
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
2095
+		 * soft deletes (trash) the object and does not permanently delete it.
2096
+		 *
2097
+		 * @param EE_Base_Class $model_object about to be 'deleted'
2098
+		 */
2099
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
2100
+		$result = $this->_delete();
2101
+		/**
2102
+		 * Called just after the `EE_Base_Class::_delete` method call.
2103
+		 * Note:
2104
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
2105
+		 * should be aware that `_delete` may not always result in a permanent delete.
2106
+		 * For example `EE_Soft_Base_Class::_delete`
2107
+		 * soft deletes (trash) the object and does not permanently delete it.
2108
+		 *
2109
+		 * @param EE_Base_Class $model_object that was just 'deleted'
2110
+		 * @param boolean       $result
2111
+		 */
2112
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
2113
+		return $result;
2114
+	}
2115
+
2116
+
2117
+	/**
2118
+	 * Calls the specific delete method for the instantiated class.
2119
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
2120
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
2121
+	 * `EE_Base_Class::delete`
2122
+	 *
2123
+	 * @return bool|int
2124
+	 * @throws ReflectionException
2125
+	 * @throws InvalidArgumentException
2126
+	 * @throws InvalidInterfaceException
2127
+	 * @throws InvalidDataTypeException
2128
+	 * @throws EE_Error
2129
+	 */
2130
+	protected function _delete()
2131
+	{
2132
+		return $this->delete_permanently();
2133
+	}
2134
+
2135
+
2136
+	/**
2137
+	 * Deletes this model object permanently from db
2138
+	 * (but keep in mind related models may block the delete and return an error)
2139
+	 *
2140
+	 * @return bool | int
2141
+	 * @throws ReflectionException
2142
+	 * @throws InvalidArgumentException
2143
+	 * @throws InvalidInterfaceException
2144
+	 * @throws InvalidDataTypeException
2145
+	 * @throws EE_Error
2146
+	 */
2147
+	public function delete_permanently()
2148
+	{
2149
+		/**
2150
+		 * Called just before HARD deleting a model object
2151
+		 *
2152
+		 * @param EE_Base_Class $model_object about to be 'deleted'
2153
+		 */
2154
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
2155
+		$result = $this->_model->delete_permanently_by_ID($this->ID());
2156
+		$this->refresh_cache_of_related_objects();
2157
+		/**
2158
+		 * Called just after HARD deleting a model object
2159
+		 *
2160
+		 * @param EE_Base_Class $model_object that was just 'deleted'
2161
+		 * @param boolean       $result
2162
+		 */
2163
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
2164
+		return $result;
2165
+	}
2166
+
2167
+
2168
+	/**
2169
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
2170
+	 * related model objects
2171
+	 *
2172
+	 * @throws ReflectionException
2173
+	 * @throws InvalidArgumentException
2174
+	 * @throws InvalidInterfaceException
2175
+	 * @throws InvalidDataTypeException
2176
+	 * @throws EE_Error
2177
+	 */
2178
+	public function refresh_cache_of_related_objects()
2179
+	{
2180
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
2181
+			if (! empty($this->_model_relations[ $relation_name ])) {
2182
+				$related_objects = $this->_model_relations[ $relation_name ];
2183
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
2184
+					// this relation only stores a single model object, not an array
2185
+					// but let's make it consistent
2186
+					$related_objects = [$related_objects];
2187
+				}
2188
+				foreach ($related_objects as $related_object) {
2189
+					// only refresh their cache if they're in memory
2190
+					if ($related_object instanceof EE_Base_Class) {
2191
+						$related_object->clear_cache(
2192
+							$this->_model->get_this_model_name(),
2193
+							$this
2194
+						);
2195
+					}
2196
+				}
2197
+			}
2198
+		}
2199
+	}
2200
+
2201
+
2202
+	/**
2203
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
2204
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
2205
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
2206
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
2207
+	 *
2208
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
2209
+	 *                                                     Eg 'Registration'
2210
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
2211
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
2212
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
2213
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
2214
+	 *                                                     this is HasMany or HABTM.
2215
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
2216
+	 *                                                     relation from all
2217
+	 * @throws InvalidArgumentException
2218
+	 * @throws InvalidInterfaceException
2219
+	 * @throws InvalidDataTypeException
2220
+	 * @throws EE_Error
2221
+	 * @throws ReflectionException
2222
+	 */
2223
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
2224
+	{
2225
+		$relationship_to_model = $this->_model->related_settings_for($relationName);
2226
+		$index_in_cache        = '';
2227
+		if (! $relationship_to_model) {
2228
+			throw new EE_Error(
2229
+				sprintf(
2230
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
2231
+					$relationName,
2232
+					get_class($this)
2233
+				)
2234
+			);
2235
+		}
2236
+		if ($clear_all) {
2237
+			$obj_removed                             = true;
2238
+			$this->_model_relations[ $relationName ] = null;
2239
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
2240
+			$obj_removed                             = $this->_model_relations[ $relationName ];
2241
+			$this->_model_relations[ $relationName ] = null;
2242
+		} else {
2243
+			if (
2244
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
2245
+				&& $object_to_remove_or_index_into_array->ID()
2246
+			) {
2247
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
2248
+				if (
2249
+					is_array($this->_model_relations[ $relationName ])
2250
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
2251
+				) {
2252
+					$index_found_at = null;
2253
+					// find this object in the array even though it has a different key
2254
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
2255
+						/** @noinspection TypeUnsafeComparisonInspection */
2256
+						if (
2257
+							$obj instanceof EE_Base_Class
2258
+							&& (
2259
+								$obj == $object_to_remove_or_index_into_array
2260
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
2261
+							)
2262
+						) {
2263
+							$index_found_at = $index;
2264
+							break;
2265
+						}
2266
+					}
2267
+					if ($index_found_at) {
2268
+						$index_in_cache = $index_found_at;
2269
+					} else {
2270
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
2271
+						// if it wasn't in it to begin with. So we're done
2272
+						return $object_to_remove_or_index_into_array;
2273
+					}
2274
+				}
2275
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
2276
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
2277
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
2278
+					/** @noinspection TypeUnsafeComparisonInspection */
2279
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
2280
+						$index_in_cache = $index;
2281
+					}
2282
+				}
2283
+			} else {
2284
+				$index_in_cache = $object_to_remove_or_index_into_array;
2285
+			}
2286
+			// supposedly we've found it. But it could just be that the client code
2287
+			// provided a bad index/object
2288
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
2289
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
2290
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
2291
+			} else {
2292
+				// that thing was never cached anyways.
2293
+				$obj_removed = null;
2294
+			}
2295
+		}
2296
+		return $obj_removed;
2297
+	}
2298
+
2299
+
2300
+	/**
2301
+	 * Saves this model object and its NEW cached relations to the database.
2302
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
2303
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
2304
+	 * because otherwise, there's a potential for infinite looping of saving
2305
+	 * Saves the cached related model objects, and ensures the relation between them
2306
+	 * and this object and properly setup
2307
+	 *
2308
+	 * @return int ID of new model object on save; 0 on failure+
2309
+	 * @throws ReflectionException
2310
+	 * @throws InvalidArgumentException
2311
+	 * @throws InvalidInterfaceException
2312
+	 * @throws InvalidDataTypeException
2313
+	 * @throws EE_Error
2314
+	 */
2315
+	public function save_new_cached_related_model_objs()
2316
+	{
2317
+		// make sure this has been saved
2318
+		if (! $this->ID()) {
2319
+			$id = $this->save();
2320
+		} else {
2321
+			$id = $this->ID();
2322
+		}
2323
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
2324
+		foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
2325
+			if ($this->_model_relations[ $relationName ]) {
2326
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2327
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2328
+				/* @var $related_model_obj EE_Base_Class */
2329
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
2330
+					// add a relation to that relation type (which saves the appropriate thing in the process)
2331
+					// but ONLY if it DOES NOT exist in the DB
2332
+					$related_model_obj = $this->_model_relations[ $relationName ];
2333
+					// if( ! $related_model_obj->ID()){
2334
+					$this->_add_relation_to($related_model_obj, $relationName);
2335
+					$related_model_obj->save_new_cached_related_model_objs();
2336
+					// }
2337
+				} else {
2338
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2339
+						// add a relation to that relation type (which saves the appropriate thing in the process)
2340
+						// but ONLY if it DOES NOT exist in the DB
2341
+						// if( ! $related_model_obj->ID()){
2342
+						$this->_add_relation_to($related_model_obj, $relationName);
2343
+						$related_model_obj->save_new_cached_related_model_objs();
2344
+						// }
2345
+					}
2346
+				}
2347
+			}
2348
+		}
2349
+		return $id;
2350
+	}
2351
+
2352
+
2353
+	/**
2354
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2355
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2356
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2357
+	 *
2358
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2359
+	 * @param string $relationName                     eg 'Events','Question',etc.
2360
+	 *                                                 an attendee to a group, you also want to specify which role they
2361
+	 *                                                 will have in that group. So you would use this parameter to
2362
+	 *                                                 specify array('role-column-name'=>'role-id')
2363
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2364
+	 *                                                 allow you to further constrict the relation to being added.
2365
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2366
+	 *                                                 column on the JOIN table and currently only the HABTM models
2367
+	 *                                                 accept these additional conditions.  Also remember that if an
2368
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2369
+	 *                                                 NEW row is created in the join table.
2370
+	 * @param null   $cache_id
2371
+	 * @return EE_Base_Class the object the relation was added to
2372
+	 * @throws ReflectionException
2373
+	 * @throws InvalidArgumentException
2374
+	 * @throws InvalidInterfaceException
2375
+	 * @throws InvalidDataTypeException
2376
+	 * @throws EE_Error
2377
+	 */
2378
+	public function _add_relation_to(
2379
+		$otherObjectModelObjectOrID,
2380
+		$relationName,
2381
+		$extra_join_model_fields_n_values = [],
2382
+		$cache_id = null
2383
+	) {
2384
+		// if this thing exists in the DB, save the relation to the DB
2385
+		if ($this->ID()) {
2386
+			$otherObject = $this->_model->add_relationship_to(
2387
+				$this,
2388
+				$otherObjectModelObjectOrID,
2389
+				$relationName,
2390
+				$extra_join_model_fields_n_values
2391
+			);
2392
+			// clear cache so future get_many_related and get_first_related() return new results.
2393
+			$this->clear_cache($relationName, $otherObject, true);
2394
+			if ($otherObject instanceof EE_Base_Class) {
2395
+				$otherObject->clear_cache($this->_model->get_this_model_name(), $this);
2396
+			}
2397
+		} else {
2398
+			// this thing doesn't exist in the DB,  so just cache it
2399
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2400
+				throw new EE_Error(
2401
+					sprintf(
2402
+						esc_html__(
2403
+							'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',
2404
+							'event_espresso'
2405
+						),
2406
+						$otherObjectModelObjectOrID,
2407
+						get_class($this)
2408
+					)
2409
+				);
2410
+			}
2411
+			$otherObject = $otherObjectModelObjectOrID;
2412
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2413
+		}
2414
+		if ($otherObject instanceof EE_Base_Class) {
2415
+			// fix the reciprocal relation too
2416
+			if ($otherObject->ID()) {
2417
+				// its saved so assumed relations exist in the DB, so we can just
2418
+				// clear the cache so future queries use the updated info in the DB
2419
+				$otherObject->clear_cache(
2420
+					$this->_model->get_this_model_name(),
2421
+					null,
2422
+					true
2423
+				);
2424
+			} else {
2425
+				// it's not saved, so it caches relations like this
2426
+				$otherObject->cache($this->_model->get_this_model_name(), $this);
2427
+			}
2428
+		}
2429
+		return $otherObject;
2430
+	}
2431
+
2432
+
2433
+	/**
2434
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2435
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2436
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2437
+	 * from the cache
2438
+	 *
2439
+	 * @param mixed  $otherObjectModelObjectOrID
2440
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2441
+	 *                to the DB yet
2442
+	 * @param string $relationName
2443
+	 * @param array  $where_query
2444
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2445
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2446
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2447
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2448
+	 *                deleted.
2449
+	 * @return EE_Base_Class the relation was removed from
2450
+	 * @throws ReflectionException
2451
+	 * @throws InvalidArgumentException
2452
+	 * @throws InvalidInterfaceException
2453
+	 * @throws InvalidDataTypeException
2454
+	 * @throws EE_Error
2455
+	 */
2456
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
2457
+	{
2458
+		if ($this->ID()) {
2459
+			// if this exists in the DB, save the relation change to the DB too
2460
+			$otherObject = $this->_model->remove_relationship_to(
2461
+				$this,
2462
+				$otherObjectModelObjectOrID,
2463
+				$relationName,
2464
+				$where_query
2465
+			);
2466
+			$this->clear_cache(
2467
+				$relationName,
2468
+				$otherObject
2469
+			);
2470
+		} else {
2471
+			// this doesn't exist in the DB, just remove it from the cache
2472
+			$otherObject = $this->clear_cache(
2473
+				$relationName,
2474
+				$otherObjectModelObjectOrID
2475
+			);
2476
+		}
2477
+		if ($otherObject instanceof EE_Base_Class) {
2478
+			$otherObject->clear_cache(
2479
+				$this->_model->get_this_model_name(),
2480
+				$this
2481
+			);
2482
+		}
2483
+		return $otherObject;
2484
+	}
2485
+
2486
+
2487
+	/**
2488
+	 * Removes ALL the related things for the $relationName.
2489
+	 *
2490
+	 * @param string $relationName
2491
+	 * @param array  $where_query_params @see
2492
+	 *                                   https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2493
+	 * @return EE_Base_Class
2494
+	 * @throws ReflectionException
2495
+	 * @throws InvalidArgumentException
2496
+	 * @throws InvalidInterfaceException
2497
+	 * @throws InvalidDataTypeException
2498
+	 * @throws EE_Error
2499
+	 */
2500
+	public function _remove_relations($relationName, $where_query_params = [])
2501
+	{
2502
+		if ($this->ID()) {
2503
+			// if this exists in the DB, save the relation change to the DB too
2504
+			$otherObjects = $this->_model->remove_relations(
2505
+				$this,
2506
+				$relationName,
2507
+				$where_query_params
2508
+			);
2509
+			$this->clear_cache(
2510
+				$relationName,
2511
+				null,
2512
+				true
2513
+			);
2514
+		} else {
2515
+			// this doesn't exist in the DB, just remove it from the cache
2516
+			$otherObjects = $this->clear_cache(
2517
+				$relationName,
2518
+				null,
2519
+				true
2520
+			);
2521
+		}
2522
+		if (is_array($otherObjects)) {
2523
+			foreach ($otherObjects as $otherObject) {
2524
+				$otherObject->clear_cache(
2525
+					$this->_model->get_this_model_name(),
2526
+					$this
2527
+				);
2528
+			}
2529
+		}
2530
+		return $otherObjects;
2531
+	}
2532
+
2533
+
2534
+	/**
2535
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2536
+	 * unless otherwise specified in the $query_params
2537
+	 *
2538
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2539
+	 * @param array  $query_params   @see
2540
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2541
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2542
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2543
+	 *                               that by the setting $distinct to TRUE;
2544
+	 * @return int
2545
+	 * @throws ReflectionException
2546
+	 * @throws InvalidArgumentException
2547
+	 * @throws InvalidInterfaceException
2548
+	 * @throws InvalidDataTypeException
2549
+	 * @throws EE_Error
2550
+	 */
2551
+	public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2552
+	{
2553
+		return $this->_model->count_related(
2554
+			$this,
2555
+			$relation_name,
2556
+			$query_params,
2557
+			$field_to_count,
2558
+			$distinct
2559
+		);
2560
+	}
2561
+
2562
+
2563
+	/**
2564
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2565
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2566
+	 *
2567
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2568
+	 * @param array  $query_params  @see
2569
+	 *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2570
+	 * @param string $field_to_sum  name of field to count by.
2571
+	 *                              By default, uses primary key
2572
+	 *                              (which doesn't make much sense, so you should probably change it)
2573
+	 * @return int
2574
+	 * @throws ReflectionException
2575
+	 * @throws InvalidArgumentException
2576
+	 * @throws InvalidInterfaceException
2577
+	 * @throws InvalidDataTypeException
2578
+	 * @throws EE_Error
2579
+	 */
2580
+	public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2581
+	{
2582
+		return $this->_model->sum_related(
2583
+			$this,
2584
+			$relation_name,
2585
+			$query_params,
2586
+			$field_to_sum
2587
+		);
2588
+	}
2589
+
2590
+
2591
+	/**
2592
+	 * Does a delete on all related objects of type $relationName and removes
2593
+	 * the current model object's relation to them. If they can't be deleted (because
2594
+	 * of blocking related model objects) does nothing. If the related model objects are
2595
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2596
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2597
+	 *
2598
+	 * @param string $relationName
2599
+	 * @param array  $query_params @see
2600
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2601
+	 * @return int how many deleted
2602
+	 * @throws ReflectionException
2603
+	 * @throws InvalidArgumentException
2604
+	 * @throws InvalidInterfaceException
2605
+	 * @throws InvalidDataTypeException
2606
+	 * @throws EE_Error
2607
+	 */
2608
+	public function delete_related($relationName, $query_params = [])
2609
+	{
2610
+		if ($this->ID()) {
2611
+			$count = $this->_model->delete_related(
2612
+				$this,
2613
+				$relationName,
2614
+				$query_params
2615
+			);
2616
+		} else {
2617
+			$count = count($this->get_all_from_cache($relationName));
2618
+			$this->clear_cache($relationName, null, true);
2619
+		}
2620
+		return $count;
2621
+	}
2622
+
2623
+
2624
+	/**
2625
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2626
+	 * the current model object's relation to them. If they can't be deleted (because
2627
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2628
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2629
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2630
+	 *
2631
+	 * @param string $relationName
2632
+	 * @param array  $query_params @see
2633
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2634
+	 * @return int how many deleted (including those soft deleted)
2635
+	 * @throws ReflectionException
2636
+	 * @throws InvalidArgumentException
2637
+	 * @throws InvalidInterfaceException
2638
+	 * @throws InvalidDataTypeException
2639
+	 * @throws EE_Error
2640
+	 */
2641
+	public function delete_related_permanently($relationName, $query_params = [])
2642
+	{
2643
+		if ($this->ID()) {
2644
+			$count = $this->_model->delete_related_permanently(
2645
+				$this,
2646
+				$relationName,
2647
+				$query_params
2648
+			);
2649
+		} else {
2650
+			$count = count($this->get_all_from_cache($relationName));
2651
+		}
2652
+		$this->clear_cache($relationName, null, true);
2653
+		return $count;
2654
+	}
2655
+
2656
+
2657
+	/**
2658
+	 * is_set
2659
+	 * Just a simple utility function children can use for checking if property exists
2660
+	 *
2661
+	 * @access  public
2662
+	 * @param string $field_name property to check
2663
+	 * @return bool                              TRUE if existing,FALSE if not.
2664
+	 */
2665
+	public function is_set($field_name)
2666
+	{
2667
+		return isset($this->_fields[ $field_name ]);
2668
+	}
2669
+
2670
+
2671
+	/**
2672
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2673
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2674
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2675
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2676
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2677
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2678
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2679
+	 * and accepts 2 arguments: the object on which the function was called,
2680
+	 * and an array of the original arguments passed to the function.
2681
+	 * Whatever their callback function returns will be returned by this function.
2682
+	 * Example: in functions.php (or in a plugin):
2683
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2684
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2685
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2686
+	 *          return $previousReturnValue.$returnString;
2687
+	 *      }
2688
+	 * require('EE_Answer.class.php');
2689
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2690
+	 * echo $answer->my_callback('monkeys',100);
2691
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2692
+	 *
2693
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2694
+	 * @param array  $args       array of original arguments passed to the function
2695
+	 * @return mixed whatever the plugin which calls add_filter decides
2696
+	 * @throws EE_Error
2697
+	 */
2698
+	public function __call($methodName, $args)
2699
+	{
2700
+		$className = get_class($this);
2701
+		$tagName   = "FHEE__{$className}__{$methodName}";
2702
+		if (! has_filter($tagName)) {
2703
+			throw new EE_Error(
2704
+				sprintf(
2705
+					esc_html__(
2706
+						"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;}",
2707
+						'event_espresso'
2708
+					),
2709
+					$methodName,
2710
+					$className,
2711
+					$tagName
2712
+				)
2713
+			);
2714
+		}
2715
+		return apply_filters($tagName, null, $this, $args);
2716
+	}
2717
+
2718
+
2719
+	/**
2720
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2721
+	 * is specified, only deletes extra meta records with that value.
2722
+	 *
2723
+	 * @param string $meta_key
2724
+	 * @param mixed  $meta_value
2725
+	 * @return int number of extra meta rows deleted
2726
+	 * @throws InvalidArgumentException
2727
+	 * @throws InvalidInterfaceException
2728
+	 * @throws InvalidDataTypeException
2729
+	 * @throws EE_Error
2730
+	 * @throws ReflectionException
2731
+	 */
2732
+	public function delete_extra_meta($meta_key, $meta_value = null)
2733
+	{
2734
+		$query_params = [
2735
+			[
2736
+				'EXM_key'  => $meta_key,
2737
+				'OBJ_ID'   => $this->ID(),
2738
+				'EXM_type' => $this->_model->get_this_model_name(),
2739
+			],
2740
+		];
2741
+		if ($meta_value !== null) {
2742
+			$query_params[0]['EXM_value'] = $meta_value;
2743
+		}
2744
+		return EEM_Extra_Meta::instance()->delete($query_params);
2745
+	}
2746
+
2747
+
2748
+	/**
2749
+	 * Returns a simple array of all the extra meta associated with this model object.
2750
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2751
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2752
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2753
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2754
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2755
+	 * finally the extra meta's value as each sub-value. (eg
2756
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2757
+	 *
2758
+	 * @param boolean $one_of_each_key
2759
+	 * @return array
2760
+	 * @throws ReflectionException
2761
+	 * @throws InvalidArgumentException
2762
+	 * @throws InvalidInterfaceException
2763
+	 * @throws InvalidDataTypeException
2764
+	 * @throws EE_Error
2765
+	 */
2766
+	public function all_extra_meta_array($one_of_each_key = true)
2767
+	{
2768
+		$return_array = [];
2769
+		if ($one_of_each_key) {
2770
+			$extra_meta_objs = $this->get_many_related(
2771
+				'Extra_Meta',
2772
+				['group_by' => 'EXM_key']
2773
+			);
2774
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2775
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2776
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2777
+				}
2778
+			}
2779
+		} else {
2780
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2781
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2782
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2783
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2784
+						$return_array[ $extra_meta_obj->key() ] = [];
2785
+					}
2786
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2787
+				}
2788
+			}
2789
+		}
2790
+		return $return_array;
2791
+	}
2792
+
2793
+
2794
+	/**
2795
+	 * refresh_from_db
2796
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
2797
+	 *
2798
+	 * @throws ReflectionException
2799
+	 * @throws InvalidArgumentException
2800
+	 * @throws InvalidInterfaceException
2801
+	 * @throws InvalidDataTypeException
2802
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2803
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2804
+	 */
2805
+	public function refresh_from_db()
2806
+	{
2807
+		if ($this->ID() && $this->in_entity_map()) {
2808
+			$this->_model->refresh_entity_map_from_db($this->ID());
2809
+		} else {
2810
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2811
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
2812
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2813
+			// absolutely nothing in it for this ID
2814
+			if (WP_DEBUG) {
2815
+				throw new EE_Error(
2816
+					sprintf(
2817
+						esc_html__(
2818
+							'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.',
2819
+							'event_espresso'
2820
+						),
2821
+						$this->ID(),
2822
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2823
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2824
+					)
2825
+				);
2826
+			}
2827
+		}
2828
+	}
2829
+
2830
+
2831
+	/**
2832
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
2833
+	 * Does not allow negative values, however.
2834
+	 *
2835
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
2836
+	 *                                   (positive or negative). One important gotcha: all these values must be
2837
+	 *                                   on the same table (eg don't pass in one field for the posts table and
2838
+	 *                                   another for the event meta table.)
2839
+	 * @return bool
2840
+	 * @throws EE_Error
2841
+	 * @throws InvalidArgumentException
2842
+	 * @throws InvalidDataTypeException
2843
+	 * @throws InvalidInterfaceException
2844
+	 * @throws ReflectionException
2845
+	 * @since 4.9.80.p
2846
+	 */
2847
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
2848
+	{
2849
+		global $wpdb;
2850
+		if (empty($fields_n_quantities)) {
2851
+			// No fields to update? Well sure, we updated them to that value just fine.
2852
+			return true;
2853
+		}
2854
+		$fields             = [];
2855
+		$set_sql_statements = [];
2856
+		foreach ($fields_n_quantities as $field_name => $quantity) {
2857
+			$field       = $this->_model->field_settings_for($field_name);
2858
+			$fields[]    = $field;
2859
+			$column_name = $field->get_table_column();
2860
+
2861
+			$abs_qty = absint($quantity);
2862
+			if ($quantity > 0) {
2863
+				// don't let the value be negative as often these fields are unsigned
2864
+				$set_sql_statements[] = $wpdb->prepare(
2865
+					"`{$column_name}` = `{$column_name}` + %d",
2866
+					$abs_qty
2867
+				);
2868
+			} else {
2869
+				$set_sql_statements[] = $wpdb->prepare(
2870
+					"`{$column_name}` = CASE
2871 2871
                        WHEN (`{$column_name}` >= %d)
2872 2872
                        THEN `{$column_name}` - %d
2873 2873
                        ELSE 0
2874 2874
                     END",
2875
-                    $abs_qty,
2876
-                    $abs_qty
2877
-                );
2878
-            }
2879
-        }
2880
-        return $this->updateFieldsInDB(
2881
-            $fields,
2882
-            implode(', ', $set_sql_statements)
2883
-        );
2884
-    }
2885
-
2886
-
2887
-    /**
2888
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
2889
-     *
2890
-     * @param EE_Model_Field_Base[] $fields
2891
-     * @param string                $new_value_sql
2892
-     *          example: 'column_name=123',
2893
-     *          or 'column_name=column_name+1',
2894
-     *          or 'column_name= CASE
2895
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
2896
-     *          THEN `column_name` + 5
2897
-     *          ELSE `column_name`
2898
-     *          END'
2899
-     *          Also updates $field on this model object with the latest value from the database.
2900
-     * @return bool
2901
-     * @throws EE_Error
2902
-     * @throws InvalidArgumentException
2903
-     * @throws InvalidDataTypeException
2904
-     * @throws InvalidInterfaceException
2905
-     * @throws ReflectionException
2906
-     * @since 4.9.80.p
2907
-     */
2908
-    protected function updateFieldsInDB($fields, $new_value_sql)
2909
-    {
2910
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
2911
-        // if it wasn't even there to start off.
2912
-        if (! $this->ID()) {
2913
-            $this->save();
2914
-        }
2915
-        global $wpdb;
2916
-        if (empty($fields)) {
2917
-            throw new InvalidArgumentException(
2918
-                esc_html__(
2919
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
2920
-                    'event_espresso'
2921
-                )
2922
-            );
2923
-        }
2924
-        $first_field = reset($fields);
2925
-        $table_alias = $first_field->get_table_alias();
2926
-        foreach ($fields as $field) {
2927
-            if ($table_alias !== $field->get_table_alias()) {
2928
-                throw new InvalidArgumentException(
2929
-                    sprintf(
2930
-                        esc_html__(
2931
-                        // @codingStandardsIgnoreStart
2932
-                            '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.',
2933
-                            // @codingStandardsIgnoreEnd
2934
-                            'event_espresso'
2935
-                        ),
2936
-                        $table_alias,
2937
-                        $field->get_table_alias()
2938
-                    )
2939
-                );
2940
-            }
2941
-        }
2942
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
2943
-        $table_obj      = $this->_model->get_table_obj_by_alias($table_alias);
2944
-        $table_pk_value = $this->ID();
2945
-        $table_name     = $table_obj->get_table_name();
2946
-        if ($table_obj instanceof EE_Secondary_Table) {
2947
-            $table_pk_field_name = $table_obj->get_fk_on_table();
2948
-        } else {
2949
-            $table_pk_field_name = $table_obj->get_pk_column();
2950
-        }
2951
-
2952
-        $query  =
2953
-            "UPDATE `{$table_name}`
2875
+					$abs_qty,
2876
+					$abs_qty
2877
+				);
2878
+			}
2879
+		}
2880
+		return $this->updateFieldsInDB(
2881
+			$fields,
2882
+			implode(', ', $set_sql_statements)
2883
+		);
2884
+	}
2885
+
2886
+
2887
+	/**
2888
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
2889
+	 *
2890
+	 * @param EE_Model_Field_Base[] $fields
2891
+	 * @param string                $new_value_sql
2892
+	 *          example: 'column_name=123',
2893
+	 *          or 'column_name=column_name+1',
2894
+	 *          or 'column_name= CASE
2895
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
2896
+	 *          THEN `column_name` + 5
2897
+	 *          ELSE `column_name`
2898
+	 *          END'
2899
+	 *          Also updates $field on this model object with the latest value from the database.
2900
+	 * @return bool
2901
+	 * @throws EE_Error
2902
+	 * @throws InvalidArgumentException
2903
+	 * @throws InvalidDataTypeException
2904
+	 * @throws InvalidInterfaceException
2905
+	 * @throws ReflectionException
2906
+	 * @since 4.9.80.p
2907
+	 */
2908
+	protected function updateFieldsInDB($fields, $new_value_sql)
2909
+	{
2910
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
2911
+		// if it wasn't even there to start off.
2912
+		if (! $this->ID()) {
2913
+			$this->save();
2914
+		}
2915
+		global $wpdb;
2916
+		if (empty($fields)) {
2917
+			throw new InvalidArgumentException(
2918
+				esc_html__(
2919
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
2920
+					'event_espresso'
2921
+				)
2922
+			);
2923
+		}
2924
+		$first_field = reset($fields);
2925
+		$table_alias = $first_field->get_table_alias();
2926
+		foreach ($fields as $field) {
2927
+			if ($table_alias !== $field->get_table_alias()) {
2928
+				throw new InvalidArgumentException(
2929
+					sprintf(
2930
+						esc_html__(
2931
+						// @codingStandardsIgnoreStart
2932
+							'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.',
2933
+							// @codingStandardsIgnoreEnd
2934
+							'event_espresso'
2935
+						),
2936
+						$table_alias,
2937
+						$field->get_table_alias()
2938
+					)
2939
+				);
2940
+			}
2941
+		}
2942
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
2943
+		$table_obj      = $this->_model->get_table_obj_by_alias($table_alias);
2944
+		$table_pk_value = $this->ID();
2945
+		$table_name     = $table_obj->get_table_name();
2946
+		if ($table_obj instanceof EE_Secondary_Table) {
2947
+			$table_pk_field_name = $table_obj->get_fk_on_table();
2948
+		} else {
2949
+			$table_pk_field_name = $table_obj->get_pk_column();
2950
+		}
2951
+
2952
+		$query  =
2953
+			"UPDATE `{$table_name}`
2954 2954
             SET "
2955
-            . $new_value_sql
2956
-            . $wpdb->prepare(
2957
-                "
2955
+			. $new_value_sql
2956
+			. $wpdb->prepare(
2957
+				"
2958 2958
             WHERE `{$table_pk_field_name}` = %d;",
2959
-                $table_pk_value
2960
-            );
2961
-        $result = $wpdb->query($query);
2962
-        foreach ($fields as $field) {
2963
-            // If it was successful, we'd like to know the new value.
2964
-            // If it failed, we'd also like to know the new value.
2965
-            $new_value = $this->_model->get_var(
2966
-                $this->_model->alter_query_params_to_restrict_by_ID(
2967
-                    $this->_model->get_index_primary_key_string(
2968
-                        $this->model_field_array()
2969
-                    ),
2970
-                    [
2971
-                        'default_where_conditions' => 'minimum',
2972
-                    ]
2973
-                ),
2974
-                $field->get_name()
2975
-            );
2976
-            $this->set_from_db(
2977
-                $field->get_name(),
2978
-                $new_value
2979
-            );
2980
-        }
2981
-        return (bool) $result;
2982
-    }
2983
-
2984
-
2985
-    /**
2986
-     * This simply returns an array of model fields for this object
2987
-     *
2988
-     * @return array
2989
-     * @throws InvalidArgumentException
2990
-     * @throws InvalidInterfaceException
2991
-     * @throws InvalidDataTypeException
2992
-     * @throws EE_Error
2993
-     */
2994
-    public function model_field_array()
2995
-    {
2996
-        $fields     = $this->_model->field_settings();
2997
-        $properties = [];
2998
-        // remove prepended underscore
2999
-        foreach ($fields as $field_name => $settings) {
3000
-            $properties[ $field_name ] = $this->get($field_name);
3001
-        }
3002
-        return $properties;
3003
-    }
3004
-
3005
-
3006
-    /**
3007
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3008
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3009
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3010
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3011
-     * Otherwise returns false.
3012
-     *
3013
-     * @param string $field_name_to_bump
3014
-     * @param string $field_name_affecting_total
3015
-     * @param string $limit_field_name
3016
-     * @param int    $quantity
3017
-     * @return bool
3018
-     * @throws EE_Error
3019
-     * @throws InvalidArgumentException
3020
-     * @throws InvalidDataTypeException
3021
-     * @throws InvalidInterfaceException
3022
-     * @throws ReflectionException
3023
-     * @since 4.9.80.p
3024
-     */
3025
-    public function incrementFieldConditionallyInDb(
3026
-        $field_name_to_bump,
3027
-        $field_name_affecting_total,
3028
-        $limit_field_name,
3029
-        $quantity
3030
-    ) {
3031
-        global $wpdb;
3032
-        $field       = $this->_model->field_settings_for($field_name_to_bump);
3033
-        $column_name = $field->get_table_column();
3034
-
3035
-        $field_affecting_total  = $this->_model->field_settings_for($field_name_affecting_total);
3036
-        $column_affecting_total = $field_affecting_total->get_table_column();
3037
-
3038
-        $limiting_field  = $this->_model->field_settings_for($limit_field_name);
3039
-        $limiting_column = $limiting_field->get_table_column();
3040
-        return $this->updateFieldsInDB(
3041
-            [$field],
3042
-            $wpdb->prepare(
3043
-                "`{$column_name}` =
2959
+				$table_pk_value
2960
+			);
2961
+		$result = $wpdb->query($query);
2962
+		foreach ($fields as $field) {
2963
+			// If it was successful, we'd like to know the new value.
2964
+			// If it failed, we'd also like to know the new value.
2965
+			$new_value = $this->_model->get_var(
2966
+				$this->_model->alter_query_params_to_restrict_by_ID(
2967
+					$this->_model->get_index_primary_key_string(
2968
+						$this->model_field_array()
2969
+					),
2970
+					[
2971
+						'default_where_conditions' => 'minimum',
2972
+					]
2973
+				),
2974
+				$field->get_name()
2975
+			);
2976
+			$this->set_from_db(
2977
+				$field->get_name(),
2978
+				$new_value
2979
+			);
2980
+		}
2981
+		return (bool) $result;
2982
+	}
2983
+
2984
+
2985
+	/**
2986
+	 * This simply returns an array of model fields for this object
2987
+	 *
2988
+	 * @return array
2989
+	 * @throws InvalidArgumentException
2990
+	 * @throws InvalidInterfaceException
2991
+	 * @throws InvalidDataTypeException
2992
+	 * @throws EE_Error
2993
+	 */
2994
+	public function model_field_array()
2995
+	{
2996
+		$fields     = $this->_model->field_settings();
2997
+		$properties = [];
2998
+		// remove prepended underscore
2999
+		foreach ($fields as $field_name => $settings) {
3000
+			$properties[ $field_name ] = $this->get($field_name);
3001
+		}
3002
+		return $properties;
3003
+	}
3004
+
3005
+
3006
+	/**
3007
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3008
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3009
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3010
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3011
+	 * Otherwise returns false.
3012
+	 *
3013
+	 * @param string $field_name_to_bump
3014
+	 * @param string $field_name_affecting_total
3015
+	 * @param string $limit_field_name
3016
+	 * @param int    $quantity
3017
+	 * @return bool
3018
+	 * @throws EE_Error
3019
+	 * @throws InvalidArgumentException
3020
+	 * @throws InvalidDataTypeException
3021
+	 * @throws InvalidInterfaceException
3022
+	 * @throws ReflectionException
3023
+	 * @since 4.9.80.p
3024
+	 */
3025
+	public function incrementFieldConditionallyInDb(
3026
+		$field_name_to_bump,
3027
+		$field_name_affecting_total,
3028
+		$limit_field_name,
3029
+		$quantity
3030
+	) {
3031
+		global $wpdb;
3032
+		$field       = $this->_model->field_settings_for($field_name_to_bump);
3033
+		$column_name = $field->get_table_column();
3034
+
3035
+		$field_affecting_total  = $this->_model->field_settings_for($field_name_affecting_total);
3036
+		$column_affecting_total = $field_affecting_total->get_table_column();
3037
+
3038
+		$limiting_field  = $this->_model->field_settings_for($limit_field_name);
3039
+		$limiting_column = $limiting_field->get_table_column();
3040
+		return $this->updateFieldsInDB(
3041
+			[$field],
3042
+			$wpdb->prepare(
3043
+				"`{$column_name}` =
3044 3044
             CASE
3045 3045
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3046 3046
                THEN `{$column_name}` + %d
3047 3047
                ELSE `{$column_name}`
3048 3048
             END",
3049
-                $quantity,
3050
-                EE_INF_IN_DB,
3051
-                $quantity
3052
-            )
3053
-        );
3054
-    }
3055
-
3056
-
3057
-    /**
3058
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3059
-     * (probably a bad assumption they have made, oh well)
3060
-     *
3061
-     * @return string
3062
-     */
3063
-    public function __toString()
3064
-    {
3065
-        try {
3066
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3067
-        } catch (Exception $e) {
3068
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3069
-            return '';
3070
-        }
3071
-    }
3072
-
3073
-
3074
-    /**
3075
-     * Gets a pretty nice displayable nice for this model object. Often overridden
3076
-     *
3077
-     * @return string
3078
-     * @throws InvalidArgumentException
3079
-     * @throws InvalidInterfaceException
3080
-     * @throws InvalidDataTypeException
3081
-     * @throws EE_Error
3082
-     */
3083
-    public function name()
3084
-    {
3085
-        // find a field that's not a text field
3086
-        $field_we_can_use = $this->_model->get_a_field_of_type('EE_Text_Field_Base');
3087
-        if ($field_we_can_use) {
3088
-            return $this->get($field_we_can_use->get_name());
3089
-        }
3090
-        $first_few_properties = $this->model_field_array();
3091
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
3092
-        $name_parts           = [];
3093
-        foreach ($first_few_properties as $name => $value) {
3094
-            $name_parts[] = "$name:$value";
3095
-        }
3096
-        return implode(',', $name_parts);
3097
-    }
3098
-
3099
-
3100
-    /**
3101
-     * Clear related model objects if they're already in the DB, because otherwise when we
3102
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3103
-     * This means if we have made changes to those related model objects, and want to unserialize
3104
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3105
-     * Instead, those related model objects should be directly serialized and stored.
3106
-     * Eg, the following won't work:
3107
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3108
-     * $att = $reg->attendee();
3109
-     * $att->set( 'ATT_fname', 'Dirk' );
3110
-     * update_option( 'my_option', serialize( $reg ) );
3111
-     * //END REQUEST
3112
-     * //START NEXT REQUEST
3113
-     * $reg = get_option( 'my_option' );
3114
-     * $reg->attendee()->save();
3115
-     * And would need to be replace with:
3116
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3117
-     * $att = $reg->attendee();
3118
-     * $att->set( 'ATT_fname', 'Dirk' );
3119
-     * update_option( 'my_option', serialize( $reg ) );
3120
-     * //END REQUEST
3121
-     * //START NEXT REQUEST
3122
-     * $att = get_option( 'my_option' );
3123
-     * $att->save();
3124
-     *
3125
-     * @return array
3126
-     * @throws ReflectionException
3127
-     * @throws InvalidArgumentException
3128
-     * @throws InvalidInterfaceException
3129
-     * @throws InvalidDataTypeException
3130
-     * @throws EE_Error
3131
-     */
3132
-    public function __sleep()
3133
-    {
3134
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3135
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3136
-                $classname = 'EE_' . $this->_model->get_this_model_name();
3137
-                if (
3138
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3139
-                    && $this->get_one_from_cache($relation_name)->ID()
3140
-                ) {
3141
-                    $this->clear_cache(
3142
-                        $relation_name,
3143
-                        $this->get_one_from_cache($relation_name)->ID()
3144
-                    );
3145
-                }
3146
-            }
3147
-        }
3148
-        $this->_props_n_values_provided_in_constructor = [];
3149
-        $properties_to_serialize                       = get_object_vars($this);
3150
-        // don't serialize the model. It's big and that risks recursion
3151
-        unset($properties_to_serialize['_model']);
3152
-        return array_keys($properties_to_serialize);
3153
-    }
3154
-
3155
-
3156
-    /**
3157
-     * restore _props_n_values_provided_in_constructor
3158
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3159
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3160
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3161
-     */
3162
-    public function __wakeup()
3163
-    {
3164
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3165
-    }
3166
-
3167
-
3168
-    /**
3169
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3170
-     * distinct with the clone host instance are also cloned.
3171
-     */
3172
-    public function __clone()
3173
-    {
3174
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3175
-        foreach ($this->_fields as $field => $value) {
3176
-            if ($value instanceof DateTime) {
3177
-                $this->_fields[ $field ] = clone $value;
3178
-            }
3179
-        }
3180
-    }
3181
-
3182
-
3183
-    /**
3184
-     * Ensures that this related thing is a model object.
3185
-     *
3186
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
3187
-     * @param string $model_name   name of the related thing, eg 'Attendee',
3188
-     * @return EE_Base_Class
3189
-     * @throws ReflectionException
3190
-     * @throws InvalidArgumentException
3191
-     * @throws InvalidInterfaceException
3192
-     * @throws InvalidDataTypeException
3193
-     * @throws EE_Error
3194
-     */
3195
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
3196
-    {
3197
-        $other_model_instance = self::_get_model_instance_with_name(
3198
-            self::_get_model_classname($model_name),
3199
-            $this->_timezone
3200
-        );
3201
-        return $other_model_instance->ensure_is_obj($object_or_id);
3202
-    }
3203
-
3204
-
3205
-    /**
3206
-     * sets the time on a datetime property
3207
-     *
3208
-     * @access protected
3209
-     * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
3210
-     * @param string          $field_name the name of the field the time is being set on (must match a
3211
-     *                                    EE_Datetime_Field)
3212
-     * @throws InvalidArgumentException
3213
-     * @throws InvalidInterfaceException
3214
-     * @throws InvalidDataTypeException
3215
-     * @throws EE_Error
3216
-     */
3217
-    protected function _set_time_for($time, $field_name)
3218
-    {
3219
-        $this->_set_date_time('T', $time, $field_name);
3220
-    }
3221
-
3222
-
3223
-    /**
3224
-     * This takes care of setting a date or time independently on a given model object property. This method also
3225
-     * verifies that the given field name matches a model object property and is for a EE_Datetime_Field field
3226
-     *
3227
-     * @access protected
3228
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
3229
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
3230
-     * @param string          $field_name     the name of the field the date OR time is being set on (must match a
3231
-     *                                        EE_Datetime_Field property)
3232
-     * @throws InvalidArgumentException
3233
-     * @throws InvalidInterfaceException
3234
-     * @throws InvalidDataTypeException
3235
-     * @throws EE_Error
3236
-     */
3237
-    protected function _set_date_time($what, $datetime_value, $field_name)
3238
-    {
3239
-        $field = $this->_get_dtt_field_settings($field_name);
3240
-        $field->set_timezone($this->_timezone);
3241
-        $field->set_date_format($this->_dt_frmt);
3242
-        $field->set_time_format($this->_tm_frmt);
3243
-        $what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
3244
-        switch ($what) {
3245
-            case 'T':
3246
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
3247
-                    $datetime_value,
3248
-                    $this->_fields[ $field_name ]
3249
-                );
3250
-                $this->_has_changes           = true;
3251
-                break;
3252
-            case 'D':
3253
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
3254
-                    $datetime_value,
3255
-                    $this->_fields[ $field_name ]
3256
-                );
3257
-                $this->_has_changes           = true;
3258
-                break;
3259
-            case 'B':
3260
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
3261
-                $this->_has_changes           = true;
3262
-                break;
3263
-        }
3264
-        $this->_clear_cached_property($field_name);
3265
-    }
3266
-
3267
-
3268
-    /**
3269
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
3270
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
3271
-     * thrown.
3272
-     *
3273
-     * @param string $field_name The field name being checked
3274
-     * @return EE_Datetime_Field
3275
-     * @throws InvalidArgumentException
3276
-     * @throws InvalidInterfaceException
3277
-     * @throws InvalidDataTypeException
3278
-     * @throws EE_Error
3279
-     */
3280
-    protected function _get_dtt_field_settings($field_name)
3281
-    {
3282
-        $field = $this->_model->field_settings_for($field_name);
3283
-        // check if field is dtt
3284
-        if ($field instanceof EE_Datetime_Field) {
3285
-            return $field;
3286
-        }
3287
-        throw new EE_Error(
3288
-            sprintf(
3289
-                esc_html__(
3290
-                    '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',
3291
-                    'event_espresso'
3292
-                ),
3293
-                $field_name,
3294
-                self::_get_model_classname(get_class($this))
3295
-            )
3296
-        );
3297
-    }
3298
-
3299
-
3300
-    /**
3301
-     * sets the date on a datetime property
3302
-     *
3303
-     * @access protected
3304
-     * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
3305
-     * @param string          $field_name the name of the field the date is being set on (must match a
3306
-     *                                    EE_Datetime_Field)
3307
-     * @throws InvalidArgumentException
3308
-     * @throws InvalidInterfaceException
3309
-     * @throws InvalidDataTypeException
3310
-     * @throws EE_Error
3311
-     */
3312
-    protected function _set_date_for($date, $field_name)
3313
-    {
3314
-        $this->_set_date_time('D', $date, $field_name);
3315
-    }
3316
-
3317
-
3318
-    /**
3319
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
3320
-     * EE_Error exception if they don't
3321
-     *
3322
-     * @param mixed (string|array) $properties properties to check
3323
-     * @return bool                              TRUE if existing, throw EE_Error if not.
3324
-     * @throws EE_Error
3325
-     */
3326
-    protected function _property_exists($properties)
3327
-    {
3328
-        foreach ((array) $properties as $property_name) {
3329
-            // first make sure this property exists
3330
-            if (! $this->_fields[ $property_name ]) {
3331
-                throw new EE_Error(
3332
-                    sprintf(
3333
-                        esc_html__(
3334
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
3335
-                            'event_espresso'
3336
-                        ),
3337
-                        $property_name
3338
-                    )
3339
-                );
3340
-            }
3341
-        }
3342
-        return true;
3343
-    }
3049
+				$quantity,
3050
+				EE_INF_IN_DB,
3051
+				$quantity
3052
+			)
3053
+		);
3054
+	}
3055
+
3056
+
3057
+	/**
3058
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3059
+	 * (probably a bad assumption they have made, oh well)
3060
+	 *
3061
+	 * @return string
3062
+	 */
3063
+	public function __toString()
3064
+	{
3065
+		try {
3066
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3067
+		} catch (Exception $e) {
3068
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3069
+			return '';
3070
+		}
3071
+	}
3072
+
3073
+
3074
+	/**
3075
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
3076
+	 *
3077
+	 * @return string
3078
+	 * @throws InvalidArgumentException
3079
+	 * @throws InvalidInterfaceException
3080
+	 * @throws InvalidDataTypeException
3081
+	 * @throws EE_Error
3082
+	 */
3083
+	public function name()
3084
+	{
3085
+		// find a field that's not a text field
3086
+		$field_we_can_use = $this->_model->get_a_field_of_type('EE_Text_Field_Base');
3087
+		if ($field_we_can_use) {
3088
+			return $this->get($field_we_can_use->get_name());
3089
+		}
3090
+		$first_few_properties = $this->model_field_array();
3091
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
3092
+		$name_parts           = [];
3093
+		foreach ($first_few_properties as $name => $value) {
3094
+			$name_parts[] = "$name:$value";
3095
+		}
3096
+		return implode(',', $name_parts);
3097
+	}
3098
+
3099
+
3100
+	/**
3101
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3102
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3103
+	 * This means if we have made changes to those related model objects, and want to unserialize
3104
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3105
+	 * Instead, those related model objects should be directly serialized and stored.
3106
+	 * Eg, the following won't work:
3107
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3108
+	 * $att = $reg->attendee();
3109
+	 * $att->set( 'ATT_fname', 'Dirk' );
3110
+	 * update_option( 'my_option', serialize( $reg ) );
3111
+	 * //END REQUEST
3112
+	 * //START NEXT REQUEST
3113
+	 * $reg = get_option( 'my_option' );
3114
+	 * $reg->attendee()->save();
3115
+	 * And would need to be replace with:
3116
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3117
+	 * $att = $reg->attendee();
3118
+	 * $att->set( 'ATT_fname', 'Dirk' );
3119
+	 * update_option( 'my_option', serialize( $reg ) );
3120
+	 * //END REQUEST
3121
+	 * //START NEXT REQUEST
3122
+	 * $att = get_option( 'my_option' );
3123
+	 * $att->save();
3124
+	 *
3125
+	 * @return array
3126
+	 * @throws ReflectionException
3127
+	 * @throws InvalidArgumentException
3128
+	 * @throws InvalidInterfaceException
3129
+	 * @throws InvalidDataTypeException
3130
+	 * @throws EE_Error
3131
+	 */
3132
+	public function __sleep()
3133
+	{
3134
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3135
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3136
+				$classname = 'EE_' . $this->_model->get_this_model_name();
3137
+				if (
3138
+					$this->get_one_from_cache($relation_name) instanceof $classname
3139
+					&& $this->get_one_from_cache($relation_name)->ID()
3140
+				) {
3141
+					$this->clear_cache(
3142
+						$relation_name,
3143
+						$this->get_one_from_cache($relation_name)->ID()
3144
+					);
3145
+				}
3146
+			}
3147
+		}
3148
+		$this->_props_n_values_provided_in_constructor = [];
3149
+		$properties_to_serialize                       = get_object_vars($this);
3150
+		// don't serialize the model. It's big and that risks recursion
3151
+		unset($properties_to_serialize['_model']);
3152
+		return array_keys($properties_to_serialize);
3153
+	}
3154
+
3155
+
3156
+	/**
3157
+	 * restore _props_n_values_provided_in_constructor
3158
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3159
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3160
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3161
+	 */
3162
+	public function __wakeup()
3163
+	{
3164
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3165
+	}
3166
+
3167
+
3168
+	/**
3169
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3170
+	 * distinct with the clone host instance are also cloned.
3171
+	 */
3172
+	public function __clone()
3173
+	{
3174
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3175
+		foreach ($this->_fields as $field => $value) {
3176
+			if ($value instanceof DateTime) {
3177
+				$this->_fields[ $field ] = clone $value;
3178
+			}
3179
+		}
3180
+	}
3181
+
3182
+
3183
+	/**
3184
+	 * Ensures that this related thing is a model object.
3185
+	 *
3186
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
3187
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
3188
+	 * @return EE_Base_Class
3189
+	 * @throws ReflectionException
3190
+	 * @throws InvalidArgumentException
3191
+	 * @throws InvalidInterfaceException
3192
+	 * @throws InvalidDataTypeException
3193
+	 * @throws EE_Error
3194
+	 */
3195
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
3196
+	{
3197
+		$other_model_instance = self::_get_model_instance_with_name(
3198
+			self::_get_model_classname($model_name),
3199
+			$this->_timezone
3200
+		);
3201
+		return $other_model_instance->ensure_is_obj($object_or_id);
3202
+	}
3203
+
3204
+
3205
+	/**
3206
+	 * sets the time on a datetime property
3207
+	 *
3208
+	 * @access protected
3209
+	 * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
3210
+	 * @param string          $field_name the name of the field the time is being set on (must match a
3211
+	 *                                    EE_Datetime_Field)
3212
+	 * @throws InvalidArgumentException
3213
+	 * @throws InvalidInterfaceException
3214
+	 * @throws InvalidDataTypeException
3215
+	 * @throws EE_Error
3216
+	 */
3217
+	protected function _set_time_for($time, $field_name)
3218
+	{
3219
+		$this->_set_date_time('T', $time, $field_name);
3220
+	}
3221
+
3222
+
3223
+	/**
3224
+	 * This takes care of setting a date or time independently on a given model object property. This method also
3225
+	 * verifies that the given field name matches a model object property and is for a EE_Datetime_Field field
3226
+	 *
3227
+	 * @access protected
3228
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
3229
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
3230
+	 * @param string          $field_name     the name of the field the date OR time is being set on (must match a
3231
+	 *                                        EE_Datetime_Field property)
3232
+	 * @throws InvalidArgumentException
3233
+	 * @throws InvalidInterfaceException
3234
+	 * @throws InvalidDataTypeException
3235
+	 * @throws EE_Error
3236
+	 */
3237
+	protected function _set_date_time($what, $datetime_value, $field_name)
3238
+	{
3239
+		$field = $this->_get_dtt_field_settings($field_name);
3240
+		$field->set_timezone($this->_timezone);
3241
+		$field->set_date_format($this->_dt_frmt);
3242
+		$field->set_time_format($this->_tm_frmt);
3243
+		$what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
3244
+		switch ($what) {
3245
+			case 'T':
3246
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
3247
+					$datetime_value,
3248
+					$this->_fields[ $field_name ]
3249
+				);
3250
+				$this->_has_changes           = true;
3251
+				break;
3252
+			case 'D':
3253
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
3254
+					$datetime_value,
3255
+					$this->_fields[ $field_name ]
3256
+				);
3257
+				$this->_has_changes           = true;
3258
+				break;
3259
+			case 'B':
3260
+				$this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
3261
+				$this->_has_changes           = true;
3262
+				break;
3263
+		}
3264
+		$this->_clear_cached_property($field_name);
3265
+	}
3266
+
3267
+
3268
+	/**
3269
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
3270
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
3271
+	 * thrown.
3272
+	 *
3273
+	 * @param string $field_name The field name being checked
3274
+	 * @return EE_Datetime_Field
3275
+	 * @throws InvalidArgumentException
3276
+	 * @throws InvalidInterfaceException
3277
+	 * @throws InvalidDataTypeException
3278
+	 * @throws EE_Error
3279
+	 */
3280
+	protected function _get_dtt_field_settings($field_name)
3281
+	{
3282
+		$field = $this->_model->field_settings_for($field_name);
3283
+		// check if field is dtt
3284
+		if ($field instanceof EE_Datetime_Field) {
3285
+			return $field;
3286
+		}
3287
+		throw new EE_Error(
3288
+			sprintf(
3289
+				esc_html__(
3290
+					'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',
3291
+					'event_espresso'
3292
+				),
3293
+				$field_name,
3294
+				self::_get_model_classname(get_class($this))
3295
+			)
3296
+		);
3297
+	}
3298
+
3299
+
3300
+	/**
3301
+	 * sets the date on a datetime property
3302
+	 *
3303
+	 * @access protected
3304
+	 * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
3305
+	 * @param string          $field_name the name of the field the date is being set on (must match a
3306
+	 *                                    EE_Datetime_Field)
3307
+	 * @throws InvalidArgumentException
3308
+	 * @throws InvalidInterfaceException
3309
+	 * @throws InvalidDataTypeException
3310
+	 * @throws EE_Error
3311
+	 */
3312
+	protected function _set_date_for($date, $field_name)
3313
+	{
3314
+		$this->_set_date_time('D', $date, $field_name);
3315
+	}
3316
+
3317
+
3318
+	/**
3319
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
3320
+	 * EE_Error exception if they don't
3321
+	 *
3322
+	 * @param mixed (string|array) $properties properties to check
3323
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
3324
+	 * @throws EE_Error
3325
+	 */
3326
+	protected function _property_exists($properties)
3327
+	{
3328
+		foreach ((array) $properties as $property_name) {
3329
+			// first make sure this property exists
3330
+			if (! $this->_fields[ $property_name ]) {
3331
+				throw new EE_Error(
3332
+					sprintf(
3333
+						esc_html__(
3334
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
3335
+							'event_espresso'
3336
+						),
3337
+						$property_name
3338
+					)
3339
+				);
3340
+			}
3341
+		}
3342
+		return true;
3343
+	}
3344 3344
 }
Please login to merge, or discard this patch.
Spacing   +121 added lines, -121 removed lines patch added patch discarded remove patch
@@ -147,7 +147,7 @@  discard block
 block discarded – undo
147 147
         $fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
148 148
         // verify client code has not passed any invalid field names
149 149
         foreach ($fieldValues as $field_name => $field_value) {
150
-            if (! isset($model_fields[ $field_name ])) {
150
+            if ( ! isset($model_fields[$field_name])) {
151 151
                 throw new EE_Error(
152 152
                     sprintf(
153 153
                         esc_html__(
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
             }
163 163
         }
164 164
         $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
165
-        if (! empty($date_formats) && is_array($date_formats)) {
165
+        if ( ! empty($date_formats) && is_array($date_formats)) {
166 166
             list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
167 167
         } else {
168 168
             // set default formats for date and time
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
             foreach ($model_fields as $fieldName => $field) {
176 176
                 $this->set_from_db(
177 177
                     $fieldName,
178
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
+                    isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null
179 179
                 );
180 180
             }
181 181
         } else {
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
             foreach ($model_fields as $fieldName => $field) {
185 185
                 $this->set(
186 186
                     $fieldName,
187
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
+                    isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null,
188 188
                     true
189 189
                 );
190 190
             }
@@ -192,15 +192,15 @@  discard block
 block discarded – undo
192 192
         // remember what values were passed to this constructor
193 193
         $this->_props_n_values_provided_in_constructor = $fieldValues;
194 194
         // remember in entity mapper
195
-        if (! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
195
+        if ( ! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
196 196
             $this->_model->add_to_entity_map($this);
197 197
         }
198 198
         // setup all the relations
199 199
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
200 200
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
-                $this->_model_relations[ $relation_name ] = null;
201
+                $this->_model_relations[$relation_name] = null;
202 202
             } else {
203
-                $this->_model_relations[ $relation_name ] = [];
203
+                $this->_model_relations[$relation_name] = [];
204 204
             }
205 205
         }
206 206
         /**
@@ -224,7 +224,7 @@  discard block
 block discarded – undo
224 224
      */
225 225
     public function get_model()
226 226
     {
227
-        if (! $this->_model) {
227
+        if ( ! $this->_model) {
228 228
             $modelName    = self::_get_model_classname(get_class($this));
229 229
             $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
230 230
         }
@@ -261,7 +261,7 @@  discard block
 block discarded – undo
261 261
             } else {
262 262
                 $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
263 263
             }
264
-            $this->_fields[ $field_name ] = $field_value;
264
+            $this->_fields[$field_name] = $field_value;
265 265
             $this->_clear_cached_property($field_name);
266 266
         }
267 267
     }
@@ -288,7 +288,7 @@  discard block
 block discarded – undo
288 288
         // then don't do anything
289 289
         if (
290 290
             ! $use_default
291
-            && $this->_fields[ $field_name ] === $field_value
291
+            && $this->_fields[$field_name] === $field_value
292 292
             && $this->ID()
293 293
         ) {
294 294
             return;
@@ -305,7 +305,7 @@  discard block
 block discarded – undo
305 305
             $holder_of_value = $field_obj->prepare_for_set($field_value);
306 306
             // should the value be null?
307 307
             if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
308
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
308
+                $this->_fields[$field_name] = $field_obj->get_default_value();
309 309
                 /**
310 310
                  * To save having to refactor all the models, if a default value is used for a
311 311
                  * EE_Datetime_Field, and that value is not null nor is it a DateTime
@@ -316,15 +316,15 @@  discard block
 block discarded – undo
316 316
                  */
317 317
                 if (
318 318
                     $field_obj instanceof EE_Datetime_Field
319
-                    && $this->_fields[ $field_name ] !== null
320
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
319
+                    && $this->_fields[$field_name] !== null
320
+                    && ! $this->_fields[$field_name] instanceof DateTime
321 321
                 ) {
322
-                    empty($this->_fields[ $field_name ])
322
+                    empty($this->_fields[$field_name])
323 323
                         ? $this->set($field_name, time())
324
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
324
+                        : $this->set($field_name, $this->_fields[$field_name]);
325 325
                 }
326 326
             } else {
327
-                $this->_fields[ $field_name ] = $holder_of_value;
327
+                $this->_fields[$field_name] = $holder_of_value;
328 328
             }
329 329
             // if we're not in the constructor...
330 330
             // now check if what we set was a primary key
@@ -383,7 +383,7 @@  discard block
 block discarded – undo
383 383
     {
384 384
         // now that we know the name of the variable, use a variable variable to get its value and return its
385 385
         if ($this->_model->has_primary_key_field()) {
386
-            return $this->_fields[ $this->_model->primary_key_name() ];
386
+            return $this->_fields[$this->_model->primary_key_name()];
387 387
         }
388 388
         return $this->_model->get_index_primary_key_string($this->_fields);
389 389
     }
@@ -401,7 +401,7 @@  discard block
 block discarded – undo
401 401
         if (strpos($model_name, 'EE_') === 0) {
402 402
             $model_classname = str_replace('EE_', 'EEM_', $model_name);
403 403
         } else {
404
-            $model_classname = 'EEM_' . $model_name;
404
+            $model_classname = 'EEM_'.$model_name;
405 405
         }
406 406
         return $model_classname;
407 407
     }
@@ -436,8 +436,8 @@  discard block
 block discarded – undo
436 436
      */
437 437
     protected function _clear_cached_property($property_name)
438 438
     {
439
-        if (isset($this->_cached_properties[ $property_name ])) {
440
-            unset($this->_cached_properties[ $property_name ]);
439
+        if (isset($this->_cached_properties[$property_name])) {
440
+            unset($this->_cached_properties[$property_name]);
441 441
         }
442 442
     }
443 443
 
@@ -458,7 +458,7 @@  discard block
 block discarded – undo
458 458
     protected static function _get_model($classname, $timezone = null)
459 459
     {
460 460
         // find model for this class
461
-        if (! $classname) {
461
+        if ( ! $classname) {
462 462
             throw new EE_Error(
463 463
                 sprintf(
464 464
                     esc_html__(
@@ -516,9 +516,9 @@  discard block
 block discarded – undo
516 516
         // verify the field exists
517 517
         $this->_model->field_settings_for($field_name);
518 518
         $cache_type = $pretty ? 'pretty' : 'standard';
519
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
520
-        if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
521
-            return $this->_cached_properties[ $field_name ][ $cache_type ];
519
+        $cache_type .= ! empty($extra_cache_ref) ? '_'.$extra_cache_ref : '';
520
+        if (isset($this->_cached_properties[$field_name][$cache_type])) {
521
+            return $this->_cached_properties[$field_name][$cache_type];
522 522
         }
523 523
         $value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
524 524
         $this->_set_cached_property($field_name, $value, $cache_type);
@@ -545,12 +545,12 @@  discard block
 block discarded – undo
545 545
         if ($field_obj instanceof EE_Datetime_Field) {
546 546
             $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
547 547
         }
548
-        if (! isset($this->_fields[ $field_name ])) {
549
-            $this->_fields[ $field_name ] = null;
548
+        if ( ! isset($this->_fields[$field_name])) {
549
+            $this->_fields[$field_name] = null;
550 550
         }
551 551
         return $pretty
552
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
553
-            : $field_obj->prepare_for_get($this->_fields[ $field_name ]);
552
+            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$field_name], $extra_cache_ref)
553
+            : $field_obj->prepare_for_get($this->_fields[$field_name]);
554 554
     }
555 555
 
556 556
 
@@ -572,7 +572,7 @@  discard block
 block discarded – undo
572 572
         // first make sure this property exists
573 573
         $this->_model->field_settings_for($field_name);
574 574
         $cache_type                                             = empty($cache_type) ? 'standard' : $cache_type;
575
-        $this->_cached_properties[ $field_name ][ $cache_type ] = $value;
575
+        $this->_cached_properties[$field_name][$cache_type] = $value;
576 576
     }
577 577
 
578 578
 
@@ -625,9 +625,9 @@  discard block
 block discarded – undo
625 625
         $primary_id_ref = self::_get_primary_key_name($classname);
626 626
         if (
627 627
             array_key_exists($primary_id_ref, $props_n_values)
628
-            && ! empty($props_n_values[ $primary_id_ref ])
628
+            && ! empty($props_n_values[$primary_id_ref])
629 629
         ) {
630
-            $id = $props_n_values[ $primary_id_ref ];
630
+            $id = $props_n_values[$primary_id_ref];
631 631
             return self::_get_model($classname)->get_from_entity_map($id);
632 632
         }
633 633
         return false;
@@ -647,7 +647,7 @@  discard block
 block discarded – undo
647 647
      */
648 648
     protected static function _get_primary_key_name($classname = null)
649 649
     {
650
-        if (! $classname) {
650
+        if ( ! $classname) {
651 651
             throw new EE_Error(
652 652
                 sprintf(
653 653
                     esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
@@ -687,10 +687,10 @@  discard block
 block discarded – undo
687 687
             $primary_id_ref = self::_get_primary_key_name($classname);
688 688
             if (
689 689
                 array_key_exists($primary_id_ref, $props_n_values)
690
-                && ! empty($props_n_values[ $primary_id_ref ])
690
+                && ! empty($props_n_values[$primary_id_ref])
691 691
             ) {
692 692
                 $existing = $model->get_one_by_ID(
693
-                    $props_n_values[ $primary_id_ref ]
693
+                    $props_n_values[$primary_id_ref]
694 694
                 );
695 695
             }
696 696
         } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
@@ -702,7 +702,7 @@  discard block
 block discarded – undo
702 702
         }
703 703
         if ($existing) {
704 704
             // set date formats if present before setting values
705
-            if (! empty($date_formats) && is_array($date_formats)) {
705
+            if ( ! empty($date_formats) && is_array($date_formats)) {
706 706
                 $existing->set_date_format($date_formats[0]);
707 707
                 $existing->set_time_format($date_formats[1]);
708 708
             } else {
@@ -788,10 +788,10 @@  discard block
 block discarded – undo
788 788
     public function get_original($field_name)
789 789
     {
790 790
         if (
791
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
791
+            isset($this->_props_n_values_provided_in_constructor[$field_name])
792 792
             && $field_settings = $this->_model->field_settings_for($field_name)
793 793
         ) {
794
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
794
+            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
795 795
         }
796 796
         return null;
797 797
     }
@@ -827,8 +827,8 @@  discard block
 block discarded – undo
827 827
      */
828 828
     public function getCustomSelect($alias)
829 829
     {
830
-        return isset($this->custom_selection_results[ $alias ])
831
-            ? $this->custom_selection_results[ $alias ]
830
+        return isset($this->custom_selection_results[$alias])
831
+            ? $this->custom_selection_results[$alias]
832 832
             : null;
833 833
     }
834 834
 
@@ -895,7 +895,7 @@  discard block
 block discarded – undo
895 895
             $this->set($column, $value);
896 896
         }
897 897
         // no changes ? then don't do anything
898
-        if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
898
+        if ( ! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
899 899
             return 0;
900 900
         }
901 901
         /**
@@ -905,7 +905,7 @@  discard block
 block discarded – undo
905 905
          * @param EE_Base_Class $model_object the model object about to be saved.
906 906
          */
907 907
         do_action('AHEE__EE_Base_Class__save__begin', $this);
908
-        if (! $this->allow_persist()) {
908
+        if ( ! $this->allow_persist()) {
909 909
             return 0;
910 910
         }
911 911
         // now get current attribute values
@@ -920,10 +920,10 @@  discard block
 block discarded – undo
920 920
         if ($this->_model->has_primary_key_field()) {
921 921
             if ($this->_model->get_primary_key_field()->is_auto_increment()) {
922 922
                 // ok check if it's set, if so: update; if not, insert
923
-                if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
923
+                if ( ! empty($save_cols_n_values[$this->_model->primary_key_name()])) {
924 924
                     $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
925 925
                 } else {
926
-                    unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
926
+                    unset($save_cols_n_values[$this->_model->primary_key_name()]);
927 927
                     $results = $this->_model->insert($save_cols_n_values);
928 928
                     if ($results) {
929 929
                         // if successful, set the primary key
@@ -933,7 +933,7 @@  discard block
 block discarded – undo
933 933
                         // will get added to the mapper before we can add this one!
934 934
                         // but if we just avoid using the SET method, all that headache can be avoided
935 935
                         $pk_field_name                   = $this->_model->primary_key_name();
936
-                        $this->_fields[ $pk_field_name ] = $results;
936
+                        $this->_fields[$pk_field_name] = $results;
937 937
                         $this->_clear_cached_property($pk_field_name);
938 938
                         $this->_model->add_to_entity_map($this);
939 939
                         $this->_update_cached_related_model_objs_fks();
@@ -950,8 +950,8 @@  discard block
 block discarded – undo
950 950
                                     'event_espresso'
951 951
                                 ),
952 952
                                 get_class($this),
953
-                                get_class($this->_model) . '::instance()->add_to_entity_map()',
954
-                                get_class($this->_model) . '::instance()->get_one_by_ID()',
953
+                                get_class($this->_model).'::instance()->add_to_entity_map()',
954
+                                get_class($this->_model).'::instance()->get_one_by_ID()',
955 955
                                 '<br />'
956 956
                             )
957 957
                         );
@@ -975,7 +975,7 @@  discard block
 block discarded – undo
975 975
                     $save_cols_n_values,
976 976
                     $this->_model->get_combined_primary_key_fields()
977 977
                 );
978
-                $results                     = $this->_model->update(
978
+                $results = $this->_model->update(
979 979
                     $save_cols_n_values,
980 980
                     $combined_pk_fields_n_values
981 981
                 );
@@ -1028,7 +1028,7 @@  discard block
 block discarded – undo
1028 1028
             $query_params[0]['EXM_value'] = $meta_value;
1029 1029
         }
1030 1030
         $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
1031
-        if (! $existing_rows_like_that) {
1031
+        if ( ! $existing_rows_like_that) {
1032 1032
             return $this->add_extra_meta($meta_key, $meta_value);
1033 1033
         }
1034 1034
         foreach ($existing_rows_like_that as $existing_row) {
@@ -1159,7 +1159,7 @@  discard block
 block discarded – undo
1159 1159
      */
1160 1160
     public function get_all_from_cache($relationName)
1161 1161
     {
1162
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
1162
+        $objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : [];
1163 1163
         // if the result is not an array, but exists, make it an array
1164 1164
         $objects = is_array($objects) ? $objects : [$objects];
1165 1165
         // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
@@ -1256,7 +1256,7 @@  discard block
 block discarded – undo
1256 1256
                 $values = [];
1257 1257
                 foreach ($results as $result) {
1258 1258
                     if ($result instanceof EE_Extra_Meta) {
1259
-                        $values[ $result->ID() ] = $result->value();
1259
+                        $values[$result->ID()] = $result->value();
1260 1260
                     }
1261 1261
                 }
1262 1262
                 return $values;
@@ -1305,7 +1305,7 @@  discard block
 block discarded – undo
1305 1305
             } else {
1306 1306
                 // first, check if we've already cached the result of this query
1307 1307
                 $cached_result = $this->get_one_from_cache($relationName);
1308
-                if (! $cached_result) {
1308
+                if ( ! $cached_result) {
1309 1309
                     $related_model_object = $this->_model->get_first_related(
1310 1310
                         $this,
1311 1311
                         $relationName,
@@ -1329,7 +1329,7 @@  discard block
 block discarded – undo
1329 1329
             }
1330 1330
             // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
1331 1331
             // just get what's cached on this object
1332
-            if (! $related_model_object) {
1332
+            if ( ! $related_model_object) {
1333 1333
                 $related_model_object = $this->get_one_from_cache($relationName);
1334 1334
             }
1335 1335
         }
@@ -1369,7 +1369,7 @@  discard block
 block discarded – undo
1369 1369
             } else {
1370 1370
                 // did we already cache the result of this query?
1371 1371
                 $cached_results = $this->get_all_from_cache($relationName);
1372
-                if (! $cached_results) {
1372
+                if ( ! $cached_results) {
1373 1373
                     $related_model_objects = $this->_model->get_all_related(
1374 1374
                         $this,
1375 1375
                         $relationName,
@@ -1401,8 +1401,8 @@  discard block
 block discarded – undo
1401 1401
      */
1402 1402
     public function get_one_from_cache($relationName)
1403 1403
     {
1404
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
1405
-            ? $this->_model_relations[ $relationName ]
1404
+        $cached_array_or_object = isset($this->_model_relations[$relationName])
1405
+            ? $this->_model_relations[$relationName]
1406 1406
             : null;
1407 1407
         if (is_array($cached_array_or_object)) {
1408 1408
             return array_shift($cached_array_or_object);
@@ -1432,11 +1432,11 @@  discard block
 block discarded – undo
1432 1432
     public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
1433 1433
     {
1434 1434
         // its entirely possible that there IS no related object yet in which case there is nothing to cache.
1435
-        if (! $object_to_cache instanceof EE_Base_Class) {
1435
+        if ( ! $object_to_cache instanceof EE_Base_Class) {
1436 1436
             return false;
1437 1437
         }
1438 1438
         // also get "how" the object is related, or throw an error
1439
-        if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1439
+        if ( ! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
1440 1440
             throw new EE_Error(
1441 1441
                 sprintf(
1442 1442
                     esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
@@ -1450,38 +1450,38 @@  discard block
 block discarded – undo
1450 1450
             // if it's a "belongs to" relationship, then there's only one related model object
1451 1451
             // eg, if this is a registration, there's only 1 attendee for it
1452 1452
             // so for these model objects just set it to be cached
1453
-            $this->_model_relations[ $relationName ] = $object_to_cache;
1453
+            $this->_model_relations[$relationName] = $object_to_cache;
1454 1454
             $return                                  = true;
1455 1455
         } else {
1456 1456
             // otherwise, this is the "many" side of a one to many relationship,
1457 1457
             // so we'll add the object to the array of related objects for that type.
1458 1458
             // eg: if this is an event, there are many registrations for that event,
1459 1459
             // so we cache the registrations in an array
1460
-            if (! is_array($this->_model_relations[ $relationName ])) {
1460
+            if ( ! is_array($this->_model_relations[$relationName])) {
1461 1461
                 // if for some reason, the cached item is a model object,
1462 1462
                 // then stick that in the array, otherwise start with an empty array
1463
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
1463
+                $this->_model_relations[$relationName] = $this->_model_relations[$relationName]
1464 1464
                                                            instanceof
1465 1465
                                                            EE_Base_Class
1466
-                    ? [$this->_model_relations[ $relationName ]] : [];
1466
+                    ? [$this->_model_relations[$relationName]] : [];
1467 1467
             }
1468 1468
             // first check for a cache_id which is normally empty
1469
-            if (! empty($cache_id)) {
1469
+            if ( ! empty($cache_id)) {
1470 1470
                 // if the cache_id exists, then it means we are purposely trying to cache this
1471 1471
                 // with a known key that can then be used to retrieve the object later on
1472
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
1472
+                $this->_model_relations[$relationName][$cache_id] = $object_to_cache;
1473 1473
                 $return                                               = $cache_id;
1474 1474
             } elseif ($object_to_cache->ID()) {
1475 1475
                 // OR the cached object originally came from the db, so let's just use it's PK for an ID
1476
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
1476
+                $this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
1477 1477
                 $return                                                            = $object_to_cache->ID();
1478 1478
             } else {
1479 1479
                 // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
1480
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
1480
+                $this->_model_relations[$relationName][] = $object_to_cache;
1481 1481
                 // move the internal pointer to the end of the array
1482
-                end($this->_model_relations[ $relationName ]);
1482
+                end($this->_model_relations[$relationName]);
1483 1483
                 // and grab the key so that we can return it
1484
-                $return = key($this->_model_relations[ $relationName ]);
1484
+                $return = key($this->_model_relations[$relationName]);
1485 1485
             }
1486 1486
         }
1487 1487
         return $return;
@@ -1523,8 +1523,8 @@  discard block
 block discarded – undo
1523 1523
         foreach ($model_fields as $field_name => $field_obj) {
1524 1524
             if ($field_obj instanceof EE_Datetime_Field) {
1525 1525
                 $field_obj->set_timezone($this->_timezone);
1526
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
1527
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
1526
+                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
1527
+                    EEH_DTT_Helper::setTimezone($this->_fields[$field_name], new DateTimeZone($this->_timezone));
1528 1528
                 }
1529 1529
             }
1530 1530
         }
@@ -1540,7 +1540,7 @@  discard block
 block discarded – undo
1540 1540
      */
1541 1541
     public function get_format($full = true)
1542 1542
     {
1543
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1543
+        return $full ? $this->_dt_frmt.' '.$this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
1544 1544
     }
1545 1545
 
1546 1546
 
@@ -1564,7 +1564,7 @@  discard block
 block discarded – undo
1564 1564
         $current_cache_id = ''
1565 1565
     ) {
1566 1566
         // verify that incoming object is of the correct type
1567
-        $obj_class = 'EE_' . $relationName;
1567
+        $obj_class = 'EE_'.$relationName;
1568 1568
         if ($newly_saved_object instanceof $obj_class) {
1569 1569
             /* @type EE_Base_Class $newly_saved_object */
1570 1570
             // now get the type of relation
@@ -1572,18 +1572,18 @@  discard block
 block discarded – undo
1572 1572
             // if this is a 1:1 relationship
1573 1573
             if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
1574 1574
                 // then just replace the cached object with the newly saved object
1575
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
1575
+                $this->_model_relations[$relationName] = $newly_saved_object;
1576 1576
                 return true;
1577 1577
                 // or if it's some kind of sordid feral polyamorous relationship...
1578 1578
             }
1579 1579
             if (
1580
-                is_array($this->_model_relations[ $relationName ])
1581
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
1580
+                is_array($this->_model_relations[$relationName])
1581
+                && isset($this->_model_relations[$relationName][$current_cache_id])
1582 1582
             ) {
1583 1583
                 // then remove the current cached item
1584
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
1584
+                unset($this->_model_relations[$relationName][$current_cache_id]);
1585 1585
                 // and cache the newly saved object using it's new ID
1586
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
1586
+                $this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
1587 1587
                 return true;
1588 1588
             }
1589 1589
         }
@@ -1726,7 +1726,7 @@  discard block
 block discarded – undo
1726 1726
     public function get_DateTime_object($field_name)
1727 1727
     {
1728 1728
         $field_settings = $this->_model->field_settings_for($field_name);
1729
-        if (! $field_settings instanceof EE_Datetime_Field) {
1729
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1730 1730
             EE_Error::add_error(
1731 1731
                 sprintf(
1732 1732
                     esc_html__(
@@ -1741,8 +1741,8 @@  discard block
 block discarded – undo
1741 1741
             );
1742 1742
             return false;
1743 1743
         }
1744
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1745
-            ? clone $this->_fields[ $field_name ]
1744
+        return isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime
1745
+            ? clone $this->_fields[$field_name]
1746 1746
             : null;
1747 1747
     }
1748 1748
 
@@ -1989,7 +1989,7 @@  discard block
 block discarded – undo
1989 1989
      */
1990 1990
     public function get_i18n_datetime($field_name, $format = '')
1991 1991
     {
1992
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1992
+        $format = empty($format) ? $this->_dt_frmt.' '.$this->_tm_frmt : $format;
1993 1993
         return date_i18n(
1994 1994
             $format,
1995 1995
             EEH_DTT_Helper::get_timestamp_with_offset(
@@ -2013,9 +2013,9 @@  discard block
 block discarded – undo
2013 2013
     public function get_raw($field_name)
2014 2014
     {
2015 2015
         $field_settings = $this->_model->field_settings_for($field_name);
2016
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
2017
-            ? $this->_fields[ $field_name ]->format('U')
2018
-            : $this->_fields[ $field_name ];
2016
+        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
2017
+            ? $this->_fields[$field_name]->format('U')
2018
+            : $this->_fields[$field_name];
2019 2019
     }
2020 2020
 
2021 2021
 
@@ -2053,7 +2053,7 @@  discard block
 block discarded – undo
2053 2053
         $this->set_timezone($timezone);
2054 2054
         $fn   = (array) $field_name;
2055 2055
         $args = array_merge($fn, (array) $args);
2056
-        if (! method_exists($this, $callback)) {
2056
+        if ( ! method_exists($this, $callback)) {
2057 2057
             throw new EE_Error(
2058 2058
                 sprintf(
2059 2059
                     esc_html__(
@@ -2065,7 +2065,7 @@  discard block
 block discarded – undo
2065 2065
             );
2066 2066
         }
2067 2067
         $args   = (array) $args;
2068
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
2068
+        $return = $prepend.call_user_func_array([$this, $callback], $args).$append;
2069 2069
         $this->set_timezone($original_timezone);
2070 2070
         return $return;
2071 2071
     }
@@ -2178,8 +2178,8 @@  discard block
 block discarded – undo
2178 2178
     public function refresh_cache_of_related_objects()
2179 2179
     {
2180 2180
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
2181
-            if (! empty($this->_model_relations[ $relation_name ])) {
2182
-                $related_objects = $this->_model_relations[ $relation_name ];
2181
+            if ( ! empty($this->_model_relations[$relation_name])) {
2182
+                $related_objects = $this->_model_relations[$relation_name];
2183 2183
                 if ($relation_obj instanceof EE_Belongs_To_Relation) {
2184 2184
                     // this relation only stores a single model object, not an array
2185 2185
                     // but let's make it consistent
@@ -2224,7 +2224,7 @@  discard block
 block discarded – undo
2224 2224
     {
2225 2225
         $relationship_to_model = $this->_model->related_settings_for($relationName);
2226 2226
         $index_in_cache        = '';
2227
-        if (! $relationship_to_model) {
2227
+        if ( ! $relationship_to_model) {
2228 2228
             throw new EE_Error(
2229 2229
                 sprintf(
2230 2230
                     esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
@@ -2235,10 +2235,10 @@  discard block
 block discarded – undo
2235 2235
         }
2236 2236
         if ($clear_all) {
2237 2237
             $obj_removed                             = true;
2238
-            $this->_model_relations[ $relationName ] = null;
2238
+            $this->_model_relations[$relationName] = null;
2239 2239
         } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
2240
-            $obj_removed                             = $this->_model_relations[ $relationName ];
2241
-            $this->_model_relations[ $relationName ] = null;
2240
+            $obj_removed                             = $this->_model_relations[$relationName];
2241
+            $this->_model_relations[$relationName] = null;
2242 2242
         } else {
2243 2243
             if (
2244 2244
                 $object_to_remove_or_index_into_array instanceof EE_Base_Class
@@ -2246,12 +2246,12 @@  discard block
 block discarded – undo
2246 2246
             ) {
2247 2247
                 $index_in_cache = $object_to_remove_or_index_into_array->ID();
2248 2248
                 if (
2249
-                    is_array($this->_model_relations[ $relationName ])
2250
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
2249
+                    is_array($this->_model_relations[$relationName])
2250
+                    && ! isset($this->_model_relations[$relationName][$index_in_cache])
2251 2251
                 ) {
2252 2252
                     $index_found_at = null;
2253 2253
                     // find this object in the array even though it has a different key
2254
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
2254
+                    foreach ($this->_model_relations[$relationName] as $index => $obj) {
2255 2255
                         /** @noinspection TypeUnsafeComparisonInspection */
2256 2256
                         if (
2257 2257
                             $obj instanceof EE_Base_Class
@@ -2285,9 +2285,9 @@  discard block
 block discarded – undo
2285 2285
             }
2286 2286
             // supposedly we've found it. But it could just be that the client code
2287 2287
             // provided a bad index/object
2288
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
2289
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
2290
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
2288
+            if (isset($this->_model_relations[$relationName][$index_in_cache])) {
2289
+                $obj_removed = $this->_model_relations[$relationName][$index_in_cache];
2290
+                unset($this->_model_relations[$relationName][$index_in_cache]);
2291 2291
             } else {
2292 2292
                 // that thing was never cached anyways.
2293 2293
                 $obj_removed = null;
@@ -2315,27 +2315,27 @@  discard block
 block discarded – undo
2315 2315
     public function save_new_cached_related_model_objs()
2316 2316
     {
2317 2317
         // make sure this has been saved
2318
-        if (! $this->ID()) {
2318
+        if ( ! $this->ID()) {
2319 2319
             $id = $this->save();
2320 2320
         } else {
2321 2321
             $id = $this->ID();
2322 2322
         }
2323 2323
         // now save all the NEW cached model objects  (ie they don't exist in the DB)
2324 2324
         foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
2325
-            if ($this->_model_relations[ $relationName ]) {
2325
+            if ($this->_model_relations[$relationName]) {
2326 2326
                 // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2327 2327
                 // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2328 2328
                 /* @var $related_model_obj EE_Base_Class */
2329 2329
                 if ($relationObj instanceof EE_Belongs_To_Relation) {
2330 2330
                     // add a relation to that relation type (which saves the appropriate thing in the process)
2331 2331
                     // but ONLY if it DOES NOT exist in the DB
2332
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2332
+                    $related_model_obj = $this->_model_relations[$relationName];
2333 2333
                     // if( ! $related_model_obj->ID()){
2334 2334
                     $this->_add_relation_to($related_model_obj, $relationName);
2335 2335
                     $related_model_obj->save_new_cached_related_model_objs();
2336 2336
                     // }
2337 2337
                 } else {
2338
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2338
+                    foreach ($this->_model_relations[$relationName] as $related_model_obj) {
2339 2339
                         // add a relation to that relation type (which saves the appropriate thing in the process)
2340 2340
                         // but ONLY if it DOES NOT exist in the DB
2341 2341
                         // if( ! $related_model_obj->ID()){
@@ -2396,7 +2396,7 @@  discard block
 block discarded – undo
2396 2396
             }
2397 2397
         } else {
2398 2398
             // this thing doesn't exist in the DB,  so just cache it
2399
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2399
+            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2400 2400
                 throw new EE_Error(
2401 2401
                     sprintf(
2402 2402
                         esc_html__(
@@ -2664,7 +2664,7 @@  discard block
 block discarded – undo
2664 2664
      */
2665 2665
     public function is_set($field_name)
2666 2666
     {
2667
-        return isset($this->_fields[ $field_name ]);
2667
+        return isset($this->_fields[$field_name]);
2668 2668
     }
2669 2669
 
2670 2670
 
@@ -2699,7 +2699,7 @@  discard block
 block discarded – undo
2699 2699
     {
2700 2700
         $className = get_class($this);
2701 2701
         $tagName   = "FHEE__{$className}__{$methodName}";
2702
-        if (! has_filter($tagName)) {
2702
+        if ( ! has_filter($tagName)) {
2703 2703
             throw new EE_Error(
2704 2704
                 sprintf(
2705 2705
                     esc_html__(
@@ -2773,17 +2773,17 @@  discard block
 block discarded – undo
2773 2773
             );
2774 2774
             foreach ($extra_meta_objs as $extra_meta_obj) {
2775 2775
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2776
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2776
+                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2777 2777
                 }
2778 2778
             }
2779 2779
         } else {
2780 2780
             $extra_meta_objs = $this->get_many_related('Extra_Meta');
2781 2781
             foreach ($extra_meta_objs as $extra_meta_obj) {
2782 2782
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2783
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2784
-                        $return_array[ $extra_meta_obj->key() ] = [];
2783
+                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2784
+                        $return_array[$extra_meta_obj->key()] = [];
2785 2785
                     }
2786
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2786
+                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2787 2787
                 }
2788 2788
             }
2789 2789
         }
@@ -2819,8 +2819,8 @@  discard block
 block discarded – undo
2819 2819
                             'event_espresso'
2820 2820
                         ),
2821 2821
                         $this->ID(),
2822
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2823
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2822
+                        get_class($this->get_model()).'::instance()->add_to_entity_map()',
2823
+                        get_class($this->get_model()).'::instance()->refresh_entity_map()'
2824 2824
                     )
2825 2825
                 );
2826 2826
             }
@@ -2909,7 +2909,7 @@  discard block
 block discarded – undo
2909 2909
     {
2910 2910
         // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
2911 2911
         // if it wasn't even there to start off.
2912
-        if (! $this->ID()) {
2912
+        if ( ! $this->ID()) {
2913 2913
             $this->save();
2914 2914
         }
2915 2915
         global $wpdb;
@@ -2949,7 +2949,7 @@  discard block
 block discarded – undo
2949 2949
             $table_pk_field_name = $table_obj->get_pk_column();
2950 2950
         }
2951 2951
 
2952
-        $query  =
2952
+        $query =
2953 2953
             "UPDATE `{$table_name}`
2954 2954
             SET "
2955 2955
             . $new_value_sql
@@ -2997,7 +2997,7 @@  discard block
 block discarded – undo
2997 2997
         $properties = [];
2998 2998
         // remove prepended underscore
2999 2999
         foreach ($fields as $field_name => $settings) {
3000
-            $properties[ $field_name ] = $this->get($field_name);
3000
+            $properties[$field_name] = $this->get($field_name);
3001 3001
         }
3002 3002
         return $properties;
3003 3003
     }
@@ -3133,7 +3133,7 @@  discard block
 block discarded – undo
3133 3133
     {
3134 3134
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3135 3135
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
3136
-                $classname = 'EE_' . $this->_model->get_this_model_name();
3136
+                $classname = 'EE_'.$this->_model->get_this_model_name();
3137 3137
                 if (
3138 3138
                     $this->get_one_from_cache($relation_name) instanceof $classname
3139 3139
                     && $this->get_one_from_cache($relation_name)->ID()
@@ -3174,7 +3174,7 @@  discard block
 block discarded – undo
3174 3174
         // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3175 3175
         foreach ($this->_fields as $field => $value) {
3176 3176
             if ($value instanceof DateTime) {
3177
-                $this->_fields[ $field ] = clone $value;
3177
+                $this->_fields[$field] = clone $value;
3178 3178
             }
3179 3179
         }
3180 3180
     }
@@ -3243,21 +3243,21 @@  discard block
 block discarded – undo
3243 3243
         $what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
3244 3244
         switch ($what) {
3245 3245
             case 'T':
3246
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
3246
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_time(
3247 3247
                     $datetime_value,
3248
-                    $this->_fields[ $field_name ]
3248
+                    $this->_fields[$field_name]
3249 3249
                 );
3250 3250
                 $this->_has_changes           = true;
3251 3251
                 break;
3252 3252
             case 'D':
3253
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
3253
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_date(
3254 3254
                     $datetime_value,
3255
-                    $this->_fields[ $field_name ]
3255
+                    $this->_fields[$field_name]
3256 3256
                 );
3257 3257
                 $this->_has_changes           = true;
3258 3258
                 break;
3259 3259
             case 'B':
3260
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
3260
+                $this->_fields[$field_name] = $field->prepare_for_set($datetime_value);
3261 3261
                 $this->_has_changes           = true;
3262 3262
                 break;
3263 3263
         }
@@ -3327,7 +3327,7 @@  discard block
 block discarded – undo
3327 3327
     {
3328 3328
         foreach ((array) $properties as $property_name) {
3329 3329
             // first make sure this property exists
3330
-            if (! $this->_fields[ $property_name ]) {
3330
+            if ( ! $this->_fields[$property_name]) {
3331 3331
                 throw new EE_Error(
3332 3332
                     sprintf(
3333 3333
                         esc_html__(
Please login to merge, or discard this patch.
core/db_models/relations/EE_Model_Relation_Base.php 3 patches
Doc Comments   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -117,7 +117,7 @@  discard block
 block discarded – undo
117 117
      * Note: If the related model is extends EEM_Soft_Delete_Base, then the related
118 118
      * model objects will only be soft-deleted.
119 119
      *
120
-     * @param EE_Base_Class|int|string $model_object_or_id
120
+     * @param EE_Base_Class|null $model_object_or_id
121 121
      * @param array                    $query_params
122 122
      * @return int of how many related models got deleted
123 123
      * @throws EE_Error
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
      * Note: If the related model is extends EEM_Soft_Delete_Base, then the related
294 294
      * model objects will only be soft-deleted.
295 295
      *
296
-     * @param EE_Base_Class|int|string $model_object_or_id
296
+     * @param EE_Base_Class|null $model_object_or_id
297 297
      * @param array                    $query_params
298 298
      * @return int of how many related models got deleted
299 299
      * @throws EE_Error
@@ -536,9 +536,9 @@  discard block
 block discarded – undo
536 536
 
537 537
     /**
538 538
      * @param        $other_table
539
-     * @param        $other_table_alias
539
+     * @param        string $other_table_alias
540 540
      * @param        $other_table_column
541
-     * @param        $this_table_alias
541
+     * @param        string $this_table_alias
542 542
      * @param        $this_table_join_column
543 543
      * @param string $extra_join_sql
544 544
      * @return string
Please login to merge, or discard this patch.
Indentation   +548 added lines, -548 removed lines patch added patch discarded remove patch
@@ -17,552 +17,552 @@
 block discarded – undo
17 17
 abstract class EE_Model_Relation_Base implements HasSchemaInterface
18 18
 {
19 19
 
20
-    /**
21
-     * @var bool
22
-     */
23
-    protected $_blocking_delete = false;
24
-
25
-    /**
26
-     * If you try to delete "this_model", and there are related "other_models",
27
-     * and this isn't null, then abandon the deletion and add this warning.
28
-     * This effectively makes it impossible to delete "this_model" while there are
29
-     * related "other_models" along this relation.
30
-     *
31
-     * @var string (internationalized)
32
-     */
33
-    protected $_blocking_delete_error_message;
34
-
35
-    /**
36
-     * The model name pointed to by this relation (ie, the model we want to establish a relationship to)
37
-     *
38
-     * @var string eg Event, Question_Group, Registration
39
-     */
40
-    private $_other_model_name;
41
-
42
-    /**
43
-     * The model name of which this relation is a component (ie, the model that called new EE_Model_Relation_Base)
44
-     *
45
-     * @var string eg Event, Question_Group, Registration
46
-     */
47
-    private $_this_model_name;
48
-
49
-    /**
50
-     * this is typically used when calling the relation models to make sure they inherit any set timezone from the
51
-     * initiating model.
52
-     *
53
-     * @var string
54
-     */
55
-    protected $_timezone;
56
-
57
-
58
-    /**
59
-     * Object representing the relationship between two models. This knows how to join the models,
60
-     * get related models across the relation, and add-and-remove the relationships.
61
-     *
62
-     * @param boolean $block_deletes                 if there are related models across this relation, block (prevent
63
-     *                                               and add an error) the deletion of this model
64
-     * @param string  $blocking_delete_error_message a customized error message on blocking deletes instead of the
65
-     *                                               default
66
-     */
67
-    public function __construct($block_deletes, $blocking_delete_error_message)
68
-    {
69
-        $this->_blocking_delete               = $block_deletes;
70
-        $this->_blocking_delete_error_message = $blocking_delete_error_message;
71
-    }
72
-
73
-
74
-    /**
75
-     * @param $this_model_name
76
-     * @param $other_model_name
77
-     * @throws EE_Error
78
-     */
79
-    public function _construct_finalize_set_models($this_model_name, $other_model_name)
80
-    {
81
-        $this->_this_model_name  = $this_model_name;
82
-        $this->_other_model_name = $other_model_name;
83
-        if (is_string($this->_blocking_delete)) {
84
-            throw new EE_Error(
85
-                sprintf(
86
-                    esc_html__(
87
-                        "When instantiating the relation of type %s from %s to %s, the \$block_deletes argument should be a boolean, not a string (%s)",
88
-                        "event_espresso"
89
-                    ),
90
-                    get_class($this),
91
-                    $this_model_name,
92
-                    $other_model_name,
93
-                    $this->_blocking_delete
94
-                )
95
-            );
96
-        }
97
-    }
98
-
99
-
100
-    /**
101
-     * entirely possible that relations may be called from a model and we need to make sure those relations have their
102
-     * timezone set correctly.
103
-     *
104
-     * @param string $timezone timezone to set.
105
-     */
106
-    public function set_timezone($timezone)
107
-    {
108
-        if (! empty($timezone)) {
109
-            $this->_timezone = $timezone;
110
-        }
111
-    }
112
-
113
-
114
-    /**
115
-     * Deletes the related model objects which meet the query parameters. If no
116
-     * parameters are specified, then all related model objects will be deleted.
117
-     * Note: If the related model is extends EEM_Soft_Delete_Base, then the related
118
-     * model objects will only be soft-deleted.
119
-     *
120
-     * @param EE_Base_Class|int|string $model_object_or_id
121
-     * @param array                    $query_params
122
-     * @return int of how many related models got deleted
123
-     * @throws EE_Error
124
-     * @throws ReflectionException
125
-     */
126
-    public function delete_all_related($model_object_or_id, $query_params = [])
127
-    {
128
-        // for each thing we would delete,
129
-        $related_model_objects = $this->get_all_related($model_object_or_id, $query_params);
130
-        // determine if it's blocked by anything else before it can be deleted
131
-        $deleted_count = 0;
132
-        foreach ($related_model_objects as $related_model_object) {
133
-            $delete_is_blocked = $this->get_other_model()->delete_is_blocked_by_related_models(
134
-                $related_model_object,
135
-                $model_object_or_id
136
-            );
137
-            /* @var $model_object_or_id EE_Base_Class */
138
-            if (! $delete_is_blocked) {
139
-                $this->remove_relation_to($model_object_or_id, $related_model_object);
140
-                $related_model_object->delete();
141
-                $deleted_count++;
142
-            }
143
-        }
144
-        return $deleted_count;
145
-    }
146
-
147
-
148
-    /**
149
-     * Gets all the model objects of type of other model related to $model_object,
150
-     * according to this relation. This is the same code for EE_HABTM_Relation and EE_Has_Many_Relation.
151
-     * For both of those child classes, $model_object must be saved so that it has an ID before querying,
152
-     * otherwise an error will be thrown. Note: by default we disable default_where_conditions
153
-     * EE_Belongs_To_Relation doesn't need to be saved before querying.
154
-     *
155
-     * @param EE_Base_Class|int $model_object_or_id                      or the primary key of this model
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
-     * @param boolean           $values_already_prepared_by_model_object @deprecated since 4.8.1
159
-     * @return EE_Base_Class[]
160
-     * @throws EE_Error
161
-     * @throws ReflectionException
162
-     */
163
-    public function get_all_related(
164
-        $model_object_or_id,
165
-        $query_params = [],
166
-        $values_already_prepared_by_model_object = false
167
-    ) {
168
-        if ($values_already_prepared_by_model_object !== false) {
169
-            EE_Error::doing_it_wrong(
170
-                'EE_Model_Relation_Base::get_all_related',
171
-                esc_html__('The argument $values_already_prepared_by_model_object is no longer used.', 'event_espresso'),
172
-                '4.8.1'
173
-            );
174
-        }
175
-        $query_params                                        =
176
-            $this->_disable_default_where_conditions_on_query_param($query_params);
177
-        $query_param_where_this_model_pk                     = $this->get_this_model()->get_this_model_name()
178
-                                                               .
179
-                                                               "."
180
-                                                               .
181
-                                                               $this->get_this_model()
182
-                                                                    ->get_primary_key_field()
183
-                                                                    ->get_name();
184
-        $model_object_id                                     = $this->_get_model_object_id($model_object_or_id);
185
-        $query_params[0][ $query_param_where_this_model_pk ] = $model_object_id;
186
-        return $this->get_other_model()->get_all($query_params);
187
-    }
188
-
189
-
190
-    /**
191
-     * Gets the model which this relation establishes the relation TO (ie,
192
-     * this relation object was defined on get_this_model(), get_other_model() is the other one)
193
-     *
194
-     * @return EEM_Base
195
-     * @throws EE_Error
196
-     * @throws ReflectionException
197
-     */
198
-    public function get_other_model()
199
-    {
200
-        return $this->_get_model($this->_other_model_name);
201
-    }
202
-
203
-
204
-    /**
205
-     * Similar to 'add_relation_to(...)', performs the opposite action of removing the relationship between the two
206
-     * model objects
207
-     *
208
-     * @param       $this_obj_or_id
209
-     * @param       $other_obj_or_id
210
-     * @param array $where_query
211
-     * @return bool
212
-     */
213
-    abstract public function remove_relation_to($this_obj_or_id, $other_obj_or_id, $where_query = []);
214
-
215
-
216
-    /**
217
-     * Alters the $query_params to disable default where conditions, unless otherwise specified
218
-     *
219
-     * @param array $query_params
220
-     * @return array
221
-     */
222
-    protected function _disable_default_where_conditions_on_query_param($query_params)
223
-    {
224
-        if (! isset($query_params['default_where_conditions'])) {
225
-            $query_params['default_where_conditions'] = 'none';
226
-        }
227
-        return $query_params;
228
-    }
229
-
230
-
231
-    /**
232
-     * Gets the model where this relation is defined.
233
-     *
234
-     * @return EEM_Base
235
-     * @throws EE_Error
236
-     * @throws ReflectionException
237
-     */
238
-    public function get_this_model()
239
-    {
240
-        return $this->_get_model($this->_this_model_name);
241
-    }
242
-
243
-
244
-    /**
245
-     * this just returns a model_object_id for incoming item that could be an object or id.
246
-     *
247
-     * @param EE_Base_Class|int $model_object_or_id model object or the primary key of this model
248
-     * @return int
249
-     * @throws EE_Error
250
-     * @throws ReflectionException
251
-     */
252
-    protected function _get_model_object_id($model_object_or_id)
253
-    {
254
-        $model_object_id = $model_object_or_id;
255
-        if ($model_object_or_id instanceof EE_Base_Class) {
256
-            $model_object_id = $model_object_or_id->ID();
257
-        }
258
-        if (! $model_object_id) {
259
-            throw new EE_Error(
260
-                sprintf(
261
-                    esc_html__(
262
-                        "Sorry, we cant get the related %s model objects to %s model object before it has an ID. You can solve that by just saving it before trying to get its related model objects",
263
-                        "event_espresso"
264
-                    ),
265
-                    $this->get_other_model()->get_this_model_name(),
266
-                    $this->get_this_model()->get_this_model_name()
267
-                )
268
-            );
269
-        }
270
-        return $model_object_id;
271
-    }
272
-
273
-
274
-    /**
275
-     * Internally used by get_this_model() and get_other_model()
276
-     *
277
-     * @param string $model_name like Event, Question_Group, etc. omit the EEM_
278
-     * @return EEM_Base
279
-     * @throws EE_Error
280
-     * @throws ReflectionException
281
-     */
282
-    protected function _get_model($model_name)
283
-    {
284
-        $modelInstance = EE_Registry::instance()->load_model($model_name);
285
-        $modelInstance->set_timezone($this->_timezone);
286
-        return $modelInstance;
287
-    }
288
-
289
-
290
-    /**
291
-     * Deletes the related model objects which meet the query parameters. If no
292
-     * parameters are specified, then all related model objects will be deleted.
293
-     * Note: If the related model is extends EEM_Soft_Delete_Base, then the related
294
-     * model objects will only be soft-deleted.
295
-     *
296
-     * @param EE_Base_Class|int|string $model_object_or_id
297
-     * @param array                    $query_params
298
-     * @return int of how many related models got deleted
299
-     * @throws EE_Error
300
-     * @throws ReflectionException
301
-     */
302
-    public function delete_related_permanently($model_object_or_id, $query_params = [])
303
-    {
304
-        // for each thing we would delete,
305
-        $related_model_objects = $this->get_all_related($model_object_or_id, $query_params);
306
-        // determine if it's blocked by anything else before it can be deleted
307
-        $deleted_count = 0;
308
-        foreach ($related_model_objects as $related_model_object) {
309
-            $delete_is_blocked = $this->get_other_model()->delete_is_blocked_by_related_models(
310
-                $related_model_object,
311
-                $model_object_or_id
312
-            );
313
-            /* @var $model_object_or_id EE_Base_Class */
314
-            if ($related_model_object instanceof EE_Soft_Delete_Base_Class) {
315
-                $this->remove_relation_to($model_object_or_id, $related_model_object);
316
-                $deleted_count++;
317
-                if (! $delete_is_blocked) {
318
-                    $related_model_object->delete_permanently();
319
-                } else {
320
-                    // delete is blocked
321
-                    // brent and darren, in this case, wanted to just soft delete it then
322
-                    $related_model_object->delete();
323
-                }
324
-            } else {
325
-                // its not a soft-deletable thing anyways. do the normal logic.
326
-                if (! $delete_is_blocked) {
327
-                    $this->remove_relation_to($model_object_or_id, $related_model_object);
328
-                    $related_model_object->delete();
329
-                    $deleted_count++;
330
-                }
331
-            }
332
-        }
333
-        return $deleted_count;
334
-    }
335
-
336
-
337
-    /**
338
-     * Gets the SQL string for performing the join between this model and the other model.
339
-     *
340
-     * @param string $model_relation_chain like 'Event.Event_Venue.Venue'
341
-     * @return string of SQL, eg "LEFT JOIN table_name AS table_alias ON this_model_primary_table.pk =
342
-     *                                     other_model_primary_table.fk" etc
343
-     */
344
-    abstract public function get_join_statement($model_relation_chain);
345
-
346
-
347
-    /**
348
-     * Adds a relationships between the two model objects provided. Each type of relationship handles this differently
349
-     * (EE_Belongs_To is a slight exception, it should more accurately be called set_relation_to(...), as this
350
-     * relationship only allows this model to be related to a single other model of this type)
351
-     *
352
-     * @param       $this_obj_or_id
353
-     * @param       $other_obj_or_id
354
-     * @param array $extra_join_model_fields_n_values
355
-     * @return EE_Base_Class the EE_Base_Class which was added as a relation. (Convenient if you only pass an ID for
356
-     *                        $other_obj_or_id)
357
-     */
358
-    abstract public function add_relation_to(
359
-        $this_obj_or_id,
360
-        $other_obj_or_id,
361
-        $extra_join_model_fields_n_values = []
362
-    );
363
-
364
-
365
-    /**
366
-     * Removes ALL relation instances for this relation obj
367
-     *
368
-     * @param EE_Base_Class|int $this_obj_or_id
369
-     * @param array             $where_query_param @see
370
-     *                                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
371
-     * @return EE_Base_Class[]
372
-     * @throws EE_Error
373
-     * @throws ReflectionException
374
-     */
375
-    public function remove_relations($this_obj_or_id, $where_query_param = [])
376
-    {
377
-        $related_things = $this->get_all_related($this_obj_or_id, [$where_query_param]);
378
-        $objs_removed   = [];
379
-        foreach ($related_things as $related_thing) {
380
-            $objs_removed[] = $this->remove_relation_to($this_obj_or_id, $related_thing);
381
-        }
382
-        return $objs_removed;
383
-    }
384
-
385
-
386
-    /**
387
-     * If you aren't allowed to delete this model when there are related models across this
388
-     * relation object, return true. Otherwise, if you can delete this model even though
389
-     * related objects exist, returns false.
390
-     *
391
-     * @return boolean
392
-     */
393
-    public function block_delete_if_related_models_exist()
394
-    {
395
-        return $this->_blocking_delete;
396
-    }
397
-
398
-
399
-    /**
400
-     * Gets the error message to show
401
-     *
402
-     * @return string
403
-     * @throws EE_Error
404
-     * @throws ReflectionException
405
-     */
406
-    public function get_deletion_error_message()
407
-    {
408
-        if ($this->_blocking_delete_error_message) {
409
-            return $this->_blocking_delete_error_message;
410
-        }
411
-        return sprintf(
412
-            esc_html__(
413
-                'This %1$s is currently linked to one or more %2$s records. If this %1$s is incorrect, then please remove it from all %3$s before attempting to delete it.',
414
-                "event_espresso"
415
-            ),
416
-            $this->get_this_model()->item_name(),
417
-            $this->get_other_model()->item_name(),
418
-            $this->get_other_model()->item_name(2)
419
-        );
420
-    }
421
-
422
-
423
-    /**
424
-     * This returns elements used to represent this field in the json schema.
425
-     *
426
-     * @link http://json-schema.org/
427
-     * @return array
428
-     * @throws EE_Error
429
-     * @throws ReflectionException
430
-     */
431
-    public function getSchema()
432
-    {
433
-        $schema = [
434
-            'description'   => $this->getSchemaDescription(),
435
-            'type'          => $this->getSchemaType(),
436
-            'relation'      => true,
437
-            'relation_type' => get_class($this),
438
-            'readonly'      => $this->getSchemaReadonly(),
439
-        ];
440
-
441
-        if ($this instanceof EE_HABTM_Relation) {
442
-            $schema['joining_model_name'] = $this->get_join_model()->get_this_model_name();
443
-        }
444
-
445
-        if ($this->getSchemaType() === 'array') {
446
-            $schema['items'] = [
447
-                'type' => 'object',
448
-            ];
449
-        }
450
-
451
-        return $schema;
452
-    }
453
-
454
-
455
-    /**
456
-     * Returns whatever is set as the nice name for the object.
457
-     *
458
-     * @return string
459
-     * @throws EE_Error
460
-     * @throws ReflectionException
461
-     */
462
-    public function getSchemaDescription()
463
-    {
464
-        $description = $this instanceof EE_Belongs_To_Relation
465
-            ? esc_html__('The related %1$s entity to the %2$s.', 'event_espresso')
466
-            : esc_html__('The related %1$s entities to the %2$s.', 'event_espresso');
467
-        return sprintf(
468
-            $description,
469
-            $this->get_other_model()->get_this_model_name(),
470
-            $this->get_this_model()->get_this_model_name()
471
-        );
472
-    }
473
-
474
-
475
-    /**
476
-     * If a child class has enum values, they should override this method and provide a simple array
477
-     * of the enum values.
478
-     * The reason this is not a property on the class is because there may be filterable enum values that
479
-     * are set on the instantiated object that could be filtered after construct.
480
-     *
481
-     * @return array
482
-     */
483
-    public function getSchemaEnum()
484
-    {
485
-        return [];
486
-    }
487
-
488
-
489
-    /**
490
-     * This returns the value of the $_schema_format property on the object.
491
-     *
492
-     * @return array
493
-     */
494
-    public function getSchemaFormat()
495
-    {
496
-        return [];
497
-    }
498
-
499
-
500
-    /**
501
-     * This is usually present when the $_schema_type property is 'object'.  Any child classes will need to override
502
-     * this method and return the properties for the schema.
503
-     * The reason this is not a property on the class is because there may be filters set on the values for the property
504
-     * that won't be exposed on construct.  For example enum type schemas may have the enum values filtered.
505
-     *
506
-     * @return array
507
-     */
508
-    public function getSchemaProperties()
509
-    {
510
-        return [];
511
-    }
512
-
513
-
514
-    /**
515
-     * This returns the value of the $_schema_readonly property on the object.
516
-     *
517
-     * @return bool
518
-     */
519
-    public function getSchemaReadonly()
520
-    {
521
-        return true;
522
-    }
523
-
524
-
525
-    /**
526
-     * Returns whatever is set as the $_schema_type property for the object.
527
-     * Note: this will automatically add 'null' to the schema if the object is_nullable()
528
-     *
529
-     * @return string|array
530
-     */
531
-    public function getSchemaType()
532
-    {
533
-        return $this instanceof EE_Belongs_To_Relation ? 'object' : 'array';
534
-    }
535
-
536
-
537
-    /**
538
-     * @param        $other_table
539
-     * @param        $other_table_alias
540
-     * @param        $other_table_column
541
-     * @param        $this_table_alias
542
-     * @param        $this_table_join_column
543
-     * @param string $extra_join_sql
544
-     * @return string
545
-     */
546
-    protected function _left_join(
547
-        $other_table,
548
-        $other_table_alias,
549
-        $other_table_column,
550
-        $this_table_alias,
551
-        $this_table_join_column,
552
-        $extra_join_sql = ''
553
-    ) {
554
-        return " LEFT JOIN " .
555
-               $other_table .
556
-               " AS " .
557
-               $other_table_alias .
558
-               " ON " .
559
-               $other_table_alias .
560
-               "." .
561
-               $other_table_column .
562
-               "=" .
563
-               $this_table_alias .
564
-               "." .
565
-               $this_table_join_column .
566
-               ($extra_join_sql ? " AND $extra_join_sql" : '');
567
-    }
20
+	/**
21
+	 * @var bool
22
+	 */
23
+	protected $_blocking_delete = false;
24
+
25
+	/**
26
+	 * If you try to delete "this_model", and there are related "other_models",
27
+	 * and this isn't null, then abandon the deletion and add this warning.
28
+	 * This effectively makes it impossible to delete "this_model" while there are
29
+	 * related "other_models" along this relation.
30
+	 *
31
+	 * @var string (internationalized)
32
+	 */
33
+	protected $_blocking_delete_error_message;
34
+
35
+	/**
36
+	 * The model name pointed to by this relation (ie, the model we want to establish a relationship to)
37
+	 *
38
+	 * @var string eg Event, Question_Group, Registration
39
+	 */
40
+	private $_other_model_name;
41
+
42
+	/**
43
+	 * The model name of which this relation is a component (ie, the model that called new EE_Model_Relation_Base)
44
+	 *
45
+	 * @var string eg Event, Question_Group, Registration
46
+	 */
47
+	private $_this_model_name;
48
+
49
+	/**
50
+	 * this is typically used when calling the relation models to make sure they inherit any set timezone from the
51
+	 * initiating model.
52
+	 *
53
+	 * @var string
54
+	 */
55
+	protected $_timezone;
56
+
57
+
58
+	/**
59
+	 * Object representing the relationship between two models. This knows how to join the models,
60
+	 * get related models across the relation, and add-and-remove the relationships.
61
+	 *
62
+	 * @param boolean $block_deletes                 if there are related models across this relation, block (prevent
63
+	 *                                               and add an error) the deletion of this model
64
+	 * @param string  $blocking_delete_error_message a customized error message on blocking deletes instead of the
65
+	 *                                               default
66
+	 */
67
+	public function __construct($block_deletes, $blocking_delete_error_message)
68
+	{
69
+		$this->_blocking_delete               = $block_deletes;
70
+		$this->_blocking_delete_error_message = $blocking_delete_error_message;
71
+	}
72
+
73
+
74
+	/**
75
+	 * @param $this_model_name
76
+	 * @param $other_model_name
77
+	 * @throws EE_Error
78
+	 */
79
+	public function _construct_finalize_set_models($this_model_name, $other_model_name)
80
+	{
81
+		$this->_this_model_name  = $this_model_name;
82
+		$this->_other_model_name = $other_model_name;
83
+		if (is_string($this->_blocking_delete)) {
84
+			throw new EE_Error(
85
+				sprintf(
86
+					esc_html__(
87
+						"When instantiating the relation of type %s from %s to %s, the \$block_deletes argument should be a boolean, not a string (%s)",
88
+						"event_espresso"
89
+					),
90
+					get_class($this),
91
+					$this_model_name,
92
+					$other_model_name,
93
+					$this->_blocking_delete
94
+				)
95
+			);
96
+		}
97
+	}
98
+
99
+
100
+	/**
101
+	 * entirely possible that relations may be called from a model and we need to make sure those relations have their
102
+	 * timezone set correctly.
103
+	 *
104
+	 * @param string $timezone timezone to set.
105
+	 */
106
+	public function set_timezone($timezone)
107
+	{
108
+		if (! empty($timezone)) {
109
+			$this->_timezone = $timezone;
110
+		}
111
+	}
112
+
113
+
114
+	/**
115
+	 * Deletes the related model objects which meet the query parameters. If no
116
+	 * parameters are specified, then all related model objects will be deleted.
117
+	 * Note: If the related model is extends EEM_Soft_Delete_Base, then the related
118
+	 * model objects will only be soft-deleted.
119
+	 *
120
+	 * @param EE_Base_Class|int|string $model_object_or_id
121
+	 * @param array                    $query_params
122
+	 * @return int of how many related models got deleted
123
+	 * @throws EE_Error
124
+	 * @throws ReflectionException
125
+	 */
126
+	public function delete_all_related($model_object_or_id, $query_params = [])
127
+	{
128
+		// for each thing we would delete,
129
+		$related_model_objects = $this->get_all_related($model_object_or_id, $query_params);
130
+		// determine if it's blocked by anything else before it can be deleted
131
+		$deleted_count = 0;
132
+		foreach ($related_model_objects as $related_model_object) {
133
+			$delete_is_blocked = $this->get_other_model()->delete_is_blocked_by_related_models(
134
+				$related_model_object,
135
+				$model_object_or_id
136
+			);
137
+			/* @var $model_object_or_id EE_Base_Class */
138
+			if (! $delete_is_blocked) {
139
+				$this->remove_relation_to($model_object_or_id, $related_model_object);
140
+				$related_model_object->delete();
141
+				$deleted_count++;
142
+			}
143
+		}
144
+		return $deleted_count;
145
+	}
146
+
147
+
148
+	/**
149
+	 * Gets all the model objects of type of other model related to $model_object,
150
+	 * according to this relation. This is the same code for EE_HABTM_Relation and EE_Has_Many_Relation.
151
+	 * For both of those child classes, $model_object must be saved so that it has an ID before querying,
152
+	 * otherwise an error will be thrown. Note: by default we disable default_where_conditions
153
+	 * EE_Belongs_To_Relation doesn't need to be saved before querying.
154
+	 *
155
+	 * @param EE_Base_Class|int $model_object_or_id                      or the primary key of this model
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
+	 * @param boolean           $values_already_prepared_by_model_object @deprecated since 4.8.1
159
+	 * @return EE_Base_Class[]
160
+	 * @throws EE_Error
161
+	 * @throws ReflectionException
162
+	 */
163
+	public function get_all_related(
164
+		$model_object_or_id,
165
+		$query_params = [],
166
+		$values_already_prepared_by_model_object = false
167
+	) {
168
+		if ($values_already_prepared_by_model_object !== false) {
169
+			EE_Error::doing_it_wrong(
170
+				'EE_Model_Relation_Base::get_all_related',
171
+				esc_html__('The argument $values_already_prepared_by_model_object is no longer used.', 'event_espresso'),
172
+				'4.8.1'
173
+			);
174
+		}
175
+		$query_params                                        =
176
+			$this->_disable_default_where_conditions_on_query_param($query_params);
177
+		$query_param_where_this_model_pk                     = $this->get_this_model()->get_this_model_name()
178
+															   .
179
+															   "."
180
+															   .
181
+															   $this->get_this_model()
182
+																	->get_primary_key_field()
183
+																	->get_name();
184
+		$model_object_id                                     = $this->_get_model_object_id($model_object_or_id);
185
+		$query_params[0][ $query_param_where_this_model_pk ] = $model_object_id;
186
+		return $this->get_other_model()->get_all($query_params);
187
+	}
188
+
189
+
190
+	/**
191
+	 * Gets the model which this relation establishes the relation TO (ie,
192
+	 * this relation object was defined on get_this_model(), get_other_model() is the other one)
193
+	 *
194
+	 * @return EEM_Base
195
+	 * @throws EE_Error
196
+	 * @throws ReflectionException
197
+	 */
198
+	public function get_other_model()
199
+	{
200
+		return $this->_get_model($this->_other_model_name);
201
+	}
202
+
203
+
204
+	/**
205
+	 * Similar to 'add_relation_to(...)', performs the opposite action of removing the relationship between the two
206
+	 * model objects
207
+	 *
208
+	 * @param       $this_obj_or_id
209
+	 * @param       $other_obj_or_id
210
+	 * @param array $where_query
211
+	 * @return bool
212
+	 */
213
+	abstract public function remove_relation_to($this_obj_or_id, $other_obj_or_id, $where_query = []);
214
+
215
+
216
+	/**
217
+	 * Alters the $query_params to disable default where conditions, unless otherwise specified
218
+	 *
219
+	 * @param array $query_params
220
+	 * @return array
221
+	 */
222
+	protected function _disable_default_where_conditions_on_query_param($query_params)
223
+	{
224
+		if (! isset($query_params['default_where_conditions'])) {
225
+			$query_params['default_where_conditions'] = 'none';
226
+		}
227
+		return $query_params;
228
+	}
229
+
230
+
231
+	/**
232
+	 * Gets the model where this relation is defined.
233
+	 *
234
+	 * @return EEM_Base
235
+	 * @throws EE_Error
236
+	 * @throws ReflectionException
237
+	 */
238
+	public function get_this_model()
239
+	{
240
+		return $this->_get_model($this->_this_model_name);
241
+	}
242
+
243
+
244
+	/**
245
+	 * this just returns a model_object_id for incoming item that could be an object or id.
246
+	 *
247
+	 * @param EE_Base_Class|int $model_object_or_id model object or the primary key of this model
248
+	 * @return int
249
+	 * @throws EE_Error
250
+	 * @throws ReflectionException
251
+	 */
252
+	protected function _get_model_object_id($model_object_or_id)
253
+	{
254
+		$model_object_id = $model_object_or_id;
255
+		if ($model_object_or_id instanceof EE_Base_Class) {
256
+			$model_object_id = $model_object_or_id->ID();
257
+		}
258
+		if (! $model_object_id) {
259
+			throw new EE_Error(
260
+				sprintf(
261
+					esc_html__(
262
+						"Sorry, we cant get the related %s model objects to %s model object before it has an ID. You can solve that by just saving it before trying to get its related model objects",
263
+						"event_espresso"
264
+					),
265
+					$this->get_other_model()->get_this_model_name(),
266
+					$this->get_this_model()->get_this_model_name()
267
+				)
268
+			);
269
+		}
270
+		return $model_object_id;
271
+	}
272
+
273
+
274
+	/**
275
+	 * Internally used by get_this_model() and get_other_model()
276
+	 *
277
+	 * @param string $model_name like Event, Question_Group, etc. omit the EEM_
278
+	 * @return EEM_Base
279
+	 * @throws EE_Error
280
+	 * @throws ReflectionException
281
+	 */
282
+	protected function _get_model($model_name)
283
+	{
284
+		$modelInstance = EE_Registry::instance()->load_model($model_name);
285
+		$modelInstance->set_timezone($this->_timezone);
286
+		return $modelInstance;
287
+	}
288
+
289
+
290
+	/**
291
+	 * Deletes the related model objects which meet the query parameters. If no
292
+	 * parameters are specified, then all related model objects will be deleted.
293
+	 * Note: If the related model is extends EEM_Soft_Delete_Base, then the related
294
+	 * model objects will only be soft-deleted.
295
+	 *
296
+	 * @param EE_Base_Class|int|string $model_object_or_id
297
+	 * @param array                    $query_params
298
+	 * @return int of how many related models got deleted
299
+	 * @throws EE_Error
300
+	 * @throws ReflectionException
301
+	 */
302
+	public function delete_related_permanently($model_object_or_id, $query_params = [])
303
+	{
304
+		// for each thing we would delete,
305
+		$related_model_objects = $this->get_all_related($model_object_or_id, $query_params);
306
+		// determine if it's blocked by anything else before it can be deleted
307
+		$deleted_count = 0;
308
+		foreach ($related_model_objects as $related_model_object) {
309
+			$delete_is_blocked = $this->get_other_model()->delete_is_blocked_by_related_models(
310
+				$related_model_object,
311
+				$model_object_or_id
312
+			);
313
+			/* @var $model_object_or_id EE_Base_Class */
314
+			if ($related_model_object instanceof EE_Soft_Delete_Base_Class) {
315
+				$this->remove_relation_to($model_object_or_id, $related_model_object);
316
+				$deleted_count++;
317
+				if (! $delete_is_blocked) {
318
+					$related_model_object->delete_permanently();
319
+				} else {
320
+					// delete is blocked
321
+					// brent and darren, in this case, wanted to just soft delete it then
322
+					$related_model_object->delete();
323
+				}
324
+			} else {
325
+				// its not a soft-deletable thing anyways. do the normal logic.
326
+				if (! $delete_is_blocked) {
327
+					$this->remove_relation_to($model_object_or_id, $related_model_object);
328
+					$related_model_object->delete();
329
+					$deleted_count++;
330
+				}
331
+			}
332
+		}
333
+		return $deleted_count;
334
+	}
335
+
336
+
337
+	/**
338
+	 * Gets the SQL string for performing the join between this model and the other model.
339
+	 *
340
+	 * @param string $model_relation_chain like 'Event.Event_Venue.Venue'
341
+	 * @return string of SQL, eg "LEFT JOIN table_name AS table_alias ON this_model_primary_table.pk =
342
+	 *                                     other_model_primary_table.fk" etc
343
+	 */
344
+	abstract public function get_join_statement($model_relation_chain);
345
+
346
+
347
+	/**
348
+	 * Adds a relationships between the two model objects provided. Each type of relationship handles this differently
349
+	 * (EE_Belongs_To is a slight exception, it should more accurately be called set_relation_to(...), as this
350
+	 * relationship only allows this model to be related to a single other model of this type)
351
+	 *
352
+	 * @param       $this_obj_or_id
353
+	 * @param       $other_obj_or_id
354
+	 * @param array $extra_join_model_fields_n_values
355
+	 * @return EE_Base_Class the EE_Base_Class which was added as a relation. (Convenient if you only pass an ID for
356
+	 *                        $other_obj_or_id)
357
+	 */
358
+	abstract public function add_relation_to(
359
+		$this_obj_or_id,
360
+		$other_obj_or_id,
361
+		$extra_join_model_fields_n_values = []
362
+	);
363
+
364
+
365
+	/**
366
+	 * Removes ALL relation instances for this relation obj
367
+	 *
368
+	 * @param EE_Base_Class|int $this_obj_or_id
369
+	 * @param array             $where_query_param @see
370
+	 *                                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
371
+	 * @return EE_Base_Class[]
372
+	 * @throws EE_Error
373
+	 * @throws ReflectionException
374
+	 */
375
+	public function remove_relations($this_obj_or_id, $where_query_param = [])
376
+	{
377
+		$related_things = $this->get_all_related($this_obj_or_id, [$where_query_param]);
378
+		$objs_removed   = [];
379
+		foreach ($related_things as $related_thing) {
380
+			$objs_removed[] = $this->remove_relation_to($this_obj_or_id, $related_thing);
381
+		}
382
+		return $objs_removed;
383
+	}
384
+
385
+
386
+	/**
387
+	 * If you aren't allowed to delete this model when there are related models across this
388
+	 * relation object, return true. Otherwise, if you can delete this model even though
389
+	 * related objects exist, returns false.
390
+	 *
391
+	 * @return boolean
392
+	 */
393
+	public function block_delete_if_related_models_exist()
394
+	{
395
+		return $this->_blocking_delete;
396
+	}
397
+
398
+
399
+	/**
400
+	 * Gets the error message to show
401
+	 *
402
+	 * @return string
403
+	 * @throws EE_Error
404
+	 * @throws ReflectionException
405
+	 */
406
+	public function get_deletion_error_message()
407
+	{
408
+		if ($this->_blocking_delete_error_message) {
409
+			return $this->_blocking_delete_error_message;
410
+		}
411
+		return sprintf(
412
+			esc_html__(
413
+				'This %1$s is currently linked to one or more %2$s records. If this %1$s is incorrect, then please remove it from all %3$s before attempting to delete it.',
414
+				"event_espresso"
415
+			),
416
+			$this->get_this_model()->item_name(),
417
+			$this->get_other_model()->item_name(),
418
+			$this->get_other_model()->item_name(2)
419
+		);
420
+	}
421
+
422
+
423
+	/**
424
+	 * This returns elements used to represent this field in the json schema.
425
+	 *
426
+	 * @link http://json-schema.org/
427
+	 * @return array
428
+	 * @throws EE_Error
429
+	 * @throws ReflectionException
430
+	 */
431
+	public function getSchema()
432
+	{
433
+		$schema = [
434
+			'description'   => $this->getSchemaDescription(),
435
+			'type'          => $this->getSchemaType(),
436
+			'relation'      => true,
437
+			'relation_type' => get_class($this),
438
+			'readonly'      => $this->getSchemaReadonly(),
439
+		];
440
+
441
+		if ($this instanceof EE_HABTM_Relation) {
442
+			$schema['joining_model_name'] = $this->get_join_model()->get_this_model_name();
443
+		}
444
+
445
+		if ($this->getSchemaType() === 'array') {
446
+			$schema['items'] = [
447
+				'type' => 'object',
448
+			];
449
+		}
450
+
451
+		return $schema;
452
+	}
453
+
454
+
455
+	/**
456
+	 * Returns whatever is set as the nice name for the object.
457
+	 *
458
+	 * @return string
459
+	 * @throws EE_Error
460
+	 * @throws ReflectionException
461
+	 */
462
+	public function getSchemaDescription()
463
+	{
464
+		$description = $this instanceof EE_Belongs_To_Relation
465
+			? esc_html__('The related %1$s entity to the %2$s.', 'event_espresso')
466
+			: esc_html__('The related %1$s entities to the %2$s.', 'event_espresso');
467
+		return sprintf(
468
+			$description,
469
+			$this->get_other_model()->get_this_model_name(),
470
+			$this->get_this_model()->get_this_model_name()
471
+		);
472
+	}
473
+
474
+
475
+	/**
476
+	 * If a child class has enum values, they should override this method and provide a simple array
477
+	 * of the enum values.
478
+	 * The reason this is not a property on the class is because there may be filterable enum values that
479
+	 * are set on the instantiated object that could be filtered after construct.
480
+	 *
481
+	 * @return array
482
+	 */
483
+	public function getSchemaEnum()
484
+	{
485
+		return [];
486
+	}
487
+
488
+
489
+	/**
490
+	 * This returns the value of the $_schema_format property on the object.
491
+	 *
492
+	 * @return array
493
+	 */
494
+	public function getSchemaFormat()
495
+	{
496
+		return [];
497
+	}
498
+
499
+
500
+	/**
501
+	 * This is usually present when the $_schema_type property is 'object'.  Any child classes will need to override
502
+	 * this method and return the properties for the schema.
503
+	 * The reason this is not a property on the class is because there may be filters set on the values for the property
504
+	 * that won't be exposed on construct.  For example enum type schemas may have the enum values filtered.
505
+	 *
506
+	 * @return array
507
+	 */
508
+	public function getSchemaProperties()
509
+	{
510
+		return [];
511
+	}
512
+
513
+
514
+	/**
515
+	 * This returns the value of the $_schema_readonly property on the object.
516
+	 *
517
+	 * @return bool
518
+	 */
519
+	public function getSchemaReadonly()
520
+	{
521
+		return true;
522
+	}
523
+
524
+
525
+	/**
526
+	 * Returns whatever is set as the $_schema_type property for the object.
527
+	 * Note: this will automatically add 'null' to the schema if the object is_nullable()
528
+	 *
529
+	 * @return string|array
530
+	 */
531
+	public function getSchemaType()
532
+	{
533
+		return $this instanceof EE_Belongs_To_Relation ? 'object' : 'array';
534
+	}
535
+
536
+
537
+	/**
538
+	 * @param        $other_table
539
+	 * @param        $other_table_alias
540
+	 * @param        $other_table_column
541
+	 * @param        $this_table_alias
542
+	 * @param        $this_table_join_column
543
+	 * @param string $extra_join_sql
544
+	 * @return string
545
+	 */
546
+	protected function _left_join(
547
+		$other_table,
548
+		$other_table_alias,
549
+		$other_table_column,
550
+		$this_table_alias,
551
+		$this_table_join_column,
552
+		$extra_join_sql = ''
553
+	) {
554
+		return " LEFT JOIN " .
555
+			   $other_table .
556
+			   " AS " .
557
+			   $other_table_alias .
558
+			   " ON " .
559
+			   $other_table_alias .
560
+			   "." .
561
+			   $other_table_column .
562
+			   "=" .
563
+			   $this_table_alias .
564
+			   "." .
565
+			   $this_table_join_column .
566
+			   ($extra_join_sql ? " AND $extra_join_sql" : '');
567
+	}
568 568
 }
Please login to merge, or discard this patch.
Spacing   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
      */
106 106
     public function set_timezone($timezone)
107 107
     {
108
-        if (! empty($timezone)) {
108
+        if ( ! empty($timezone)) {
109 109
             $this->_timezone = $timezone;
110 110
         }
111 111
     }
@@ -135,7 +135,7 @@  discard block
 block discarded – undo
135 135
                 $model_object_or_id
136 136
             );
137 137
             /* @var $model_object_or_id EE_Base_Class */
138
-            if (! $delete_is_blocked) {
138
+            if ( ! $delete_is_blocked) {
139 139
                 $this->remove_relation_to($model_object_or_id, $related_model_object);
140 140
                 $related_model_object->delete();
141 141
                 $deleted_count++;
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
                                                                     ->get_primary_key_field()
183 183
                                                                     ->get_name();
184 184
         $model_object_id                                     = $this->_get_model_object_id($model_object_or_id);
185
-        $query_params[0][ $query_param_where_this_model_pk ] = $model_object_id;
185
+        $query_params[0][$query_param_where_this_model_pk] = $model_object_id;
186 186
         return $this->get_other_model()->get_all($query_params);
187 187
     }
188 188
 
@@ -221,7 +221,7 @@  discard block
 block discarded – undo
221 221
      */
222 222
     protected function _disable_default_where_conditions_on_query_param($query_params)
223 223
     {
224
-        if (! isset($query_params['default_where_conditions'])) {
224
+        if ( ! isset($query_params['default_where_conditions'])) {
225 225
             $query_params['default_where_conditions'] = 'none';
226 226
         }
227 227
         return $query_params;
@@ -255,7 +255,7 @@  discard block
 block discarded – undo
255 255
         if ($model_object_or_id instanceof EE_Base_Class) {
256 256
             $model_object_id = $model_object_or_id->ID();
257 257
         }
258
-        if (! $model_object_id) {
258
+        if ( ! $model_object_id) {
259 259
             throw new EE_Error(
260 260
                 sprintf(
261 261
                     esc_html__(
@@ -314,7 +314,7 @@  discard block
 block discarded – undo
314 314
             if ($related_model_object instanceof EE_Soft_Delete_Base_Class) {
315 315
                 $this->remove_relation_to($model_object_or_id, $related_model_object);
316 316
                 $deleted_count++;
317
-                if (! $delete_is_blocked) {
317
+                if ( ! $delete_is_blocked) {
318 318
                     $related_model_object->delete_permanently();
319 319
                 } else {
320 320
                     // delete is blocked
@@ -323,7 +323,7 @@  discard block
 block discarded – undo
323 323
                 }
324 324
             } else {
325 325
                 // its not a soft-deletable thing anyways. do the normal logic.
326
-                if (! $delete_is_blocked) {
326
+                if ( ! $delete_is_blocked) {
327 327
                     $this->remove_relation_to($model_object_or_id, $related_model_object);
328 328
                     $related_model_object->delete();
329 329
                     $deleted_count++;
@@ -551,18 +551,18 @@  discard block
 block discarded – undo
551 551
         $this_table_join_column,
552 552
         $extra_join_sql = ''
553 553
     ) {
554
-        return " LEFT JOIN " .
555
-               $other_table .
556
-               " AS " .
557
-               $other_table_alias .
558
-               " ON " .
559
-               $other_table_alias .
560
-               "." .
561
-               $other_table_column .
562
-               "=" .
563
-               $this_table_alias .
564
-               "." .
565
-               $this_table_join_column .
554
+        return " LEFT JOIN ".
555
+               $other_table.
556
+               " AS ".
557
+               $other_table_alias.
558
+               " ON ".
559
+               $other_table_alias.
560
+               ".".
561
+               $other_table_column.
562
+               "=".
563
+               $this_table_alias.
564
+               ".".
565
+               $this_table_join_column.
566 566
                ($extra_join_sql ? " AND $extra_join_sql" : '');
567 567
     }
568 568
 }
Please login to merge, or discard this patch.