Completed
Branch models-cleanup/main (a73ceb)
by
unknown
54:20 queued 44:39
created
core/db_classes/EE_Base_Class.class.php 3 patches
Doc Comments   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -530,7 +530,7 @@  discard block
 block discarded – undo
530 530
      *
531 531
      * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
532 532
      *                             where the first value is the date format and the second value is the time format.
533
-     * @return mixed string|array
533
+     * @return string string|array
534 534
      */
535 535
     public function get_format($full = true)
536 536
     {
@@ -703,7 +703,7 @@  discard block
 block discarded – undo
703 703
      *
704 704
      * @param EE_Datetime_Field $datetime_field
705 705
      * @param bool              $pretty
706
-     * @param null              $date_or_time
706
+     * @param string|null              $date_or_time
707 707
      * @return void
708 708
      * @throws InvalidArgumentException
709 709
      * @throws InvalidInterfaceException
@@ -1056,7 +1056,7 @@  discard block
 block discarded – undo
1056 1056
      *
1057 1057
      * @param null  $field_to_order_by  What field is being used as the reference point.
1058 1058
      * @param array $query_params       Any additional conditions on the query.
1059
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1059
+     * @param string  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1060 1060
      *                                  you can indicate just the columns you want returned
1061 1061
      * @return array|EE_Base_Class
1062 1062
      * @throws ReflectionException
@@ -1084,7 +1084,7 @@  discard block
 block discarded – undo
1084 1084
      *
1085 1085
      * @param null  $field_to_order_by  What field is being used as the reference point.
1086 1086
      * @param array $query_params       Any additional conditions on the query.
1087
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1087
+     * @param string  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1088 1088
      *                                  you can indicate just the column you want returned
1089 1089
      * @return array|EE_Base_Class
1090 1090
      * @throws ReflectionException
@@ -1505,7 +1505,7 @@  discard block
 block discarded – undo
1505 1505
      * sets the time on a datetime property
1506 1506
      *
1507 1507
      * @access protected
1508
-     * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
1508
+     * @param string $time       a valid time string for php datetime functions (or DateTime object)
1509 1509
      * @param string          $field_name the name of the field the time is being set on (must match a
1510 1510
      *                                    EE_Datetime_Field)
1511 1511
      * @throws InvalidArgumentException
@@ -1523,7 +1523,7 @@  discard block
 block discarded – undo
1523 1523
      * sets the date on a datetime property
1524 1524
      *
1525 1525
      * @access protected
1526
-     * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
1526
+     * @param string $date       a valid date string for php datetime functions ( or DateTime object)
1527 1527
      * @param string          $field_name the name of the field the date is being set on (must match a
1528 1528
      *                                    EE_Datetime_Field)
1529 1529
      * @throws InvalidArgumentException
@@ -1700,7 +1700,7 @@  discard block
 block discarded – undo
1700 1700
      * Deletes this model object permanently from db
1701 1701
      * (but keep in mind related models may block the delete and return an error)
1702 1702
      *
1703
-     * @return bool | int
1703
+     * @return integer | int
1704 1704
      * @throws ReflectionException
1705 1705
      * @throws InvalidArgumentException
1706 1706
      * @throws InvalidInterfaceException
@@ -2043,7 +2043,7 @@  discard block
 block discarded – undo
2043 2043
      *
2044 2044
      * @param array  $props_n_values    incoming array of properties and their values
2045 2045
      * @param string $classname         the classname of the child class
2046
-     * @param null   $timezone
2046
+     * @param string|null   $timezone
2047 2047
      * @param array  $date_formats      incoming date_formats in an array where the first value is the
2048 2048
      *                                  date_format and the second value is the time format
2049 2049
      * @return mixed (EE_Base_Class|bool)
@@ -2131,7 +2131,7 @@  discard block
 block discarded – undo
2131 2131
      * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2132 2132
      *
2133 2133
      * @param string $model_classname
2134
-     * @param null   $timezone
2134
+     * @param string|null   $timezone
2135 2135
      * @return EEM_Base
2136 2136
      * @throws ReflectionException
2137 2137
      * @throws InvalidArgumentException
@@ -2488,7 +2488,7 @@  discard block
 block discarded – undo
2488 2488
      * @param string $field_to_sum  name of field to count by.
2489 2489
      *                              By default, uses primary key
2490 2490
      *                              (which doesn't make much sense, so you should probably change it)
2491
-     * @return int
2491
+     * @return double
2492 2492
      * @throws ReflectionException
2493 2493
      * @throws InvalidArgumentException
2494 2494
      * @throws InvalidInterfaceException
Please login to merge, or discard this patch.
Indentation   +3314 added lines, -3314 removed lines patch added patch discarded remove patch
@@ -13,3329 +13,3329 @@
 block discarded – undo
13 13
 abstract class EE_Base_Class
14 14
 {
15 15
 
16
-    /**
17
-     * This is an array of the original properties and values provided during construction
18
-     * of this model object. (keys are model field names, values are their values).
19
-     * This list is important to remember so that when we are merging data from the db, we know
20
-     * which values to override and which to not override.
21
-     *
22
-     * @var array
23
-     */
24
-    protected $_props_n_values_provided_in_constructor;
25
-
26
-    /**
27
-     * Timezone
28
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
-     * access to it.
32
-     *
33
-     * @var string
34
-     */
35
-    protected $_timezone;
36
-
37
-    /**
38
-     * date format
39
-     * pattern or format for displaying dates
40
-     *
41
-     * @var string $_dt_frmt
42
-     */
43
-    protected $_dt_frmt;
44
-
45
-    /**
46
-     * time format
47
-     * pattern or format for displaying time
48
-     *
49
-     * @var string $_tm_frmt
50
-     */
51
-    protected $_tm_frmt;
52
-
53
-    /**
54
-     * This property is for holding a cached array of object properties indexed by property name as the key.
55
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
56
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
-     *
59
-     * @var array
60
-     */
61
-    protected $_cached_properties = [];
62
-
63
-    /**
64
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
65
-     * single
66
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
-     * all others have an array)
69
-     *
70
-     * @var array
71
-     */
72
-    protected $_model_relations = [];
73
-
74
-    /**
75
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
-     *
78
-     * @var array
79
-     */
80
-    protected $_fields = [];
81
-
82
-    /**
83
-     * @var boolean indicating whether or not this model object is intended to ever be saved
84
-     * For example, we might create model objects intended to only be used for the duration
85
-     * of this request and to be thrown away, and if they were accidentally saved
86
-     * it would be a bug.
87
-     */
88
-    protected $_allow_persist = true;
89
-
90
-    /**
91
-     * @var boolean indicating whether or not this model object's properties have changed since construction
92
-     */
93
-    protected $_has_changes = false;
94
-
95
-    /**
96
-     * @var EEM_Base
97
-     */
98
-    protected $_model;
99
-
100
-    /**
101
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
-     * array as:
107
-     * array(
108
-     *  'Registration_Count' => 24
109
-     * );
110
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
-     * info)
113
-     *
114
-     * @var array
115
-     */
116
-    protected $custom_selection_results = [];
117
-
118
-
119
-    /**
120
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
-     * play nice
122
-     *
123
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
-     *                                                         TXN_amount, QST_name, etc) and values are their values
126
-     * @param boolean $by_db                                   a flag for setting if the class is instantiated by the
127
-     *                                                         corresponding db model or not.
128
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
-     *                                                         be in when instantiating a EE_Base_Class object.
130
-     * @param array   $date_formats                            An array of date formats to set on construct where first
131
-     *                                                         value is the date_format and second value is the time
132
-     *                                                         format.
133
-     * @throws InvalidArgumentException
134
-     * @throws InvalidInterfaceException
135
-     * @throws InvalidDataTypeException
136
-     * @throws EE_Error
137
-     * @throws ReflectionException
138
-     */
139
-    protected function __construct($fieldValues = [], $by_db = false, $timezone = '', $date_formats = [])
140
-    {
141
-        $className = get_class($this);
142
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
-        $this->_model = $this->get_model();
144
-        $model_fields = $this->_model->field_settings();
145
-        // ensure $fieldValues is an array
146
-        $fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
147
-        // verify client code has not passed any invalid field names
148
-        foreach ($fieldValues as $field_name => $field_value) {
149
-            if (! isset($model_fields[ $field_name ])) {
150
-                throw new EE_Error(
151
-                    sprintf(
152
-                        esc_html__(
153
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
-                            'event_espresso'
155
-                        ),
156
-                        $field_name,
157
-                        get_class($this),
158
-                        implode(', ', array_keys($model_fields))
159
-                    )
160
-                );
161
-            }
162
-        }
163
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
-        if (! empty($date_formats) && is_array($date_formats)) {
165
-            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
166
-        } else {
167
-            // set default formats for date and time
168
-            $this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
169
-            $this->_tm_frmt = (string)get_option('time_format', 'g:i a');
170
-        }
171
-        // if db model is instantiating
172
-        if ($by_db) {
173
-            // client code has indicated these field values are from the database
174
-            foreach ($model_fields as $fieldName => $field) {
175
-                $this->set_from_db(
176
-                    $fieldName,
177
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
-                );
179
-            }
180
-        } else {
181
-            // we're constructing a brand
182
-            // new instance of the model object. Generally, this means we'll need to do more field validation
183
-            foreach ($model_fields as $fieldName => $field) {
184
-                $this->set(
185
-                    $fieldName,
186
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
-                    true
188
-                );
189
-            }
190
-        }
191
-        // remember what values were passed to this constructor
192
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
193
-        // remember in entity mapper
194
-        if (! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
195
-            $this->_model->add_to_entity_map($this);
196
-        }
197
-        // setup all the relations
198
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
199
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
-                $this->_model_relations[ $relation_name ] = null;
201
-            } else {
202
-                $this->_model_relations[ $relation_name ] = [];
203
-            }
204
-        }
205
-        /**
206
-         * Action done at the end of each model object construction
207
-         *
208
-         * @param EE_Base_Class $this the model object just created
209
-         */
210
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
-    }
212
-
213
-
214
-    /**
215
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
216
-     *
217
-     * @return boolean
218
-     */
219
-    public function allow_persist()
220
-    {
221
-        return $this->_allow_persist;
222
-    }
223
-
224
-
225
-    /**
226
-     * Sets whether or not this model object should be allowed to be saved to the DB.
227
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
-     * you got new information that somehow made you change your mind.
229
-     *
230
-     * @param boolean $allow_persist
231
-     * @return boolean
232
-     */
233
-    public function set_allow_persist($allow_persist)
234
-    {
235
-        return $this->_allow_persist = $allow_persist;
236
-    }
237
-
238
-
239
-    /**
240
-     * Gets the field's original value when this object was constructed during this request.
241
-     * This can be helpful when determining if a model object has changed or not
242
-     *
243
-     * @param string $field_name
244
-     * @return mixed|null
245
-     * @throws InvalidArgumentException
246
-     * @throws InvalidInterfaceException
247
-     * @throws InvalidDataTypeException
248
-     * @throws EE_Error
249
-     */
250
-    public function get_original($field_name)
251
-    {
252
-        if (
253
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
254
-            && $field_settings = $this->_model->field_settings_for($field_name)
255
-        ) {
256
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
257
-        }
258
-        return null;
259
-    }
260
-
261
-
262
-    /**
263
-     * @param EE_Base_Class $obj
264
-     * @return string
265
-     */
266
-    public function get_class($obj)
267
-    {
268
-        return get_class($obj);
269
-    }
270
-
271
-
272
-    /**
273
-     * Overrides parent because parent expects old models.
274
-     * This also doesn't do any validation, and won't work for serialized arrays
275
-     *
276
-     * @param string $field_name
277
-     * @param mixed  $field_value
278
-     * @param bool   $use_default
279
-     * @throws InvalidArgumentException
280
-     * @throws InvalidInterfaceException
281
-     * @throws InvalidDataTypeException
282
-     * @throws EE_Error
283
-     * @throws ReflectionException
284
-     * @throws ReflectionException
285
-     * @throws ReflectionException
286
-     */
287
-    public function set($field_name, $field_value, $use_default = false)
288
-    {
289
-        // if not using default and nothing has changed, and object has already been setup (has ID),
290
-        // then don't do anything
291
-        if (
292
-            ! $use_default
293
-            && $this->_fields[ $field_name ] === $field_value
294
-            && $this->ID()
295
-        ) {
296
-            return;
297
-        }
298
-        $this->_has_changes = true;
299
-        $field_obj          = $this->_model->field_settings_for($field_name);
300
-        if ($field_obj instanceof EE_Model_Field_Base) {
301
-            // if ( method_exists( $field_obj, 'set_timezone' )) {
302
-            if ($field_obj instanceof EE_Datetime_Field) {
303
-                $field_obj->set_timezone($this->_timezone);
304
-                $field_obj->set_date_format($this->_dt_frmt);
305
-                $field_obj->set_time_format($this->_tm_frmt);
306
-            }
307
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
308
-            // should the value be null?
309
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
310
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
311
-                /**
312
-                 * To save having to refactor all the models, if a default value is used for a
313
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
314
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
315
-                 * object.
316
-                 *
317
-                 * @since 4.6.10+
318
-                 */
319
-                if (
320
-                    $field_obj instanceof EE_Datetime_Field
321
-                    && $this->_fields[ $field_name ] !== null
322
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
323
-                ) {
324
-                    empty($this->_fields[ $field_name ])
325
-                        ? $this->set($field_name, time())
326
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
327
-                }
328
-            } else {
329
-                $this->_fields[ $field_name ] = $holder_of_value;
330
-            }
331
-            // if we're not in the constructor...
332
-            // now check if what we set was a primary key
333
-            if (
334
-                // note: props_n_values_provided_in_constructor is only set at the END of the constructor
335
-                $this->_props_n_values_provided_in_constructor
336
-                && $field_value
337
-                && $field_name === $this->_model->primary_key_name()
338
-            ) {
339
-                // if so, we want all this object's fields to be filled either with
340
-                // what we've explicitly set on this model
341
-                // or what we have in the db
342
-                // echo "setting primary key!";
343
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
344
-                $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
345
-                foreach ($fields_on_model as $field_obj) {
346
-                    if (
347
-                        ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
348
-                        && $field_obj->get_name() !== $field_name
349
-                    ) {
350
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
351
-                    }
352
-                }
353
-                // oh this model object has an ID? well make sure its in the entity mapper
354
-                $this->_model->add_to_entity_map($this);
355
-            }
356
-            // let's unset any cache for this field_name from the $_cached_properties property.
357
-            $this->_clear_cached_property($field_name);
358
-        } else {
359
-            throw new EE_Error(
360
-                sprintf(
361
-                    esc_html__(
362
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
363
-                        'event_espresso'
364
-                    ),
365
-                    $field_name
366
-                )
367
-            );
368
-        }
369
-    }
370
-
371
-
372
-    /**
373
-     * Set custom select values for model.
374
-     *
375
-     * @param array $custom_select_values
376
-     */
377
-    public function setCustomSelectsValues(array $custom_select_values)
378
-    {
379
-        $this->custom_selection_results = $custom_select_values;
380
-    }
381
-
382
-
383
-    /**
384
-     * Returns the custom select value for the provided alias if its set.
385
-     * If not set, returns null.
386
-     *
387
-     * @param string $alias
388
-     * @return string|int|float|null
389
-     */
390
-    public function getCustomSelect($alias)
391
-    {
392
-        return isset($this->custom_selection_results[ $alias ])
393
-            ? $this->custom_selection_results[ $alias ]
394
-            : null;
395
-    }
396
-
397
-
398
-    /**
399
-     * This sets the field value on the db column if it exists for the given $column_name or
400
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
401
-     *
402
-     * @param string $field_name  Must be the exact column name.
403
-     * @param mixed  $field_value The value to set.
404
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
405
-     * @throws InvalidArgumentException
406
-     * @throws InvalidInterfaceException
407
-     * @throws InvalidDataTypeException
408
-     * @throws EE_Error
409
-     * @throws ReflectionException
410
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
411
-     */
412
-    public function set_field_or_extra_meta($field_name, $field_value)
413
-    {
414
-        if ($this->_model->has_field($field_name)) {
415
-            $this->set($field_name, $field_value);
416
-            return true;
417
-        }
418
-        // ensure this object is saved first so that extra meta can be properly related.
419
-        $this->save();
420
-        return $this->update_extra_meta($field_name, $field_value);
421
-    }
422
-
423
-
424
-    /**
425
-     * This retrieves the value of the db column set on this class or if that's not present
426
-     * it will attempt to retrieve from extra_meta if found.
427
-     * Example Usage:
428
-     * Via EE_Message child class:
429
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
430
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
431
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
432
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
433
-     * value for those extra fields dynamically via the EE_message object.
434
-     *
435
-     * @param string $field_name expecting the fully qualified field name.
436
-     * @return mixed|null  value for the field if found.  null if not found.
437
-     * @throws ReflectionException
438
-     * @throws InvalidArgumentException
439
-     * @throws InvalidInterfaceException
440
-     * @throws InvalidDataTypeException
441
-     * @throws EE_Error
442
-     */
443
-    public function get_field_or_extra_meta($field_name)
444
-    {
445
-        if ($this->_model->has_field($field_name)) {
446
-            $column_value = $this->get($field_name);
447
-        } else {
448
-            // This isn't a column in the main table, let's see if it is in the extra meta.
449
-            $column_value = $this->get_extra_meta($field_name, true);
450
-        }
451
-        return $column_value;
452
-    }
453
-
454
-
455
-    /**
456
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
457
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
458
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
459
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
460
-     *
461
-     * @access public
462
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
463
-     * @return void
464
-     * @throws InvalidArgumentException
465
-     * @throws InvalidInterfaceException
466
-     * @throws InvalidDataTypeException
467
-     */
468
-    public function set_timezone($timezone = '')
469
-    {
470
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
471
-        // make sure we clear all cached properties because they won't be relevant now
472
-        $this->_clear_cached_properties();
473
-        // make sure we update field settings and the date for all EE_Datetime_Fields
474
-        $model_fields = $this->_model->field_settings();
475
-        foreach ($model_fields as $field_name => $field_obj) {
476
-            if ($field_obj instanceof EE_Datetime_Field) {
477
-                $field_obj->set_timezone($this->_timezone);
478
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
479
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
480
-                }
481
-            }
482
-        }
483
-    }
484
-
485
-
486
-    /**
487
-     * This just returns whatever is set for the current timezone.
488
-     *
489
-     * @access public
490
-     * @return string timezone string
491
-     */
492
-    public function get_timezone()
493
-    {
494
-        return $this->_timezone;
495
-    }
496
-
497
-
498
-    /**
499
-     * This sets the internal date format to what is sent in to be used as the new default for the class
500
-     * internally instead of wp set date format options
501
-     *
502
-     * @param string $format should be a format recognizable by PHP date() functions.
503
-     * @since 4.6
504
-     */
505
-    public function set_date_format($format)
506
-    {
507
-        $this->_dt_frmt = $format;
508
-        // clear cached_properties because they won't be relevant now.
509
-        $this->_clear_cached_properties();
510
-    }
511
-
512
-
513
-    /**
514
-     * This sets the internal time format string to what is sent in to be used as the new default for the
515
-     * class internally instead of wp set time format options.
516
-     *
517
-     * @param string $format should be a format recognizable by PHP date() functions.
518
-     * @since 4.6
519
-     */
520
-    public function set_time_format($format)
521
-    {
522
-        $this->_tm_frmt = $format;
523
-        // clear cached_properties because they won't be relevant now.
524
-        $this->_clear_cached_properties();
525
-    }
526
-
527
-
528
-    /**
529
-     * This returns the current internal set format for the date and time formats.
530
-     *
531
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
532
-     *                             where the first value is the date format and the second value is the time format.
533
-     * @return mixed string|array
534
-     */
535
-    public function get_format($full = true)
536
-    {
537
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
538
-    }
539
-
540
-
541
-    /**
542
-     * cache
543
-     * stores the passed model object on the current model object.
544
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
545
-     *
546
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
547
-     *                                       'Registration' associated with this model object
548
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
549
-     *                                       that could be a payment or a registration)
550
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
551
-     *                                       items which will be stored in an array on this object
552
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
553
-     *                                       related thing, no array)
554
-     * @throws InvalidArgumentException
555
-     * @throws InvalidInterfaceException
556
-     * @throws InvalidDataTypeException
557
-     * @throws EE_Error
558
-     */
559
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
560
-    {
561
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
562
-        if (! $object_to_cache instanceof EE_Base_Class) {
563
-            return false;
564
-        }
565
-        // also get "how" the object is related, or throw an error
566
-        if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
567
-            throw new EE_Error(
568
-                sprintf(
569
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
570
-                    $relationName,
571
-                    get_class($this)
572
-                )
573
-            );
574
-        }
575
-        // how many things are related ?
576
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
577
-            // if it's a "belongs to" relationship, then there's only one related model object
578
-            // eg, if this is a registration, there's only 1 attendee for it
579
-            // so for these model objects just set it to be cached
580
-            $this->_model_relations[ $relationName ] = $object_to_cache;
581
-            $return                                  = true;
582
-        } else {
583
-            // otherwise, this is the "many" side of a one to many relationship,
584
-            // so we'll add the object to the array of related objects for that type.
585
-            // eg: if this is an event, there are many registrations for that event,
586
-            // so we cache the registrations in an array
587
-            if (! is_array($this->_model_relations[ $relationName ])) {
588
-                // if for some reason, the cached item is a model object,
589
-                // then stick that in the array, otherwise start with an empty array
590
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
591
-                                                           instanceof
592
-                                                           EE_Base_Class
593
-                    ? [$this->_model_relations[ $relationName ]] : [];
594
-            }
595
-            // first check for a cache_id which is normally empty
596
-            if (! empty($cache_id)) {
597
-                // if the cache_id exists, then it means we are purposely trying to cache this
598
-                // with a known key that can then be used to retrieve the object later on
599
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
600
-                $return                                               = $cache_id;
601
-            } elseif ($object_to_cache->ID()) {
602
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
603
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
604
-                $return                                                            = $object_to_cache->ID();
605
-            } else {
606
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
607
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
608
-                // move the internal pointer to the end of the array
609
-                end($this->_model_relations[ $relationName ]);
610
-                // and grab the key so that we can return it
611
-                $return = key($this->_model_relations[ $relationName ]);
612
-            }
613
-        }
614
-        return $return;
615
-    }
616
-
617
-
618
-    /**
619
-     * For adding an item to the cached_properties property.
620
-     *
621
-     * @access protected
622
-     * @param string      $field_name the property item the corresponding value is for.
623
-     * @param mixed       $value      The value we are caching.
624
-     * @param string|null $cache_type
625
-     * @return void
626
-     * @throws InvalidArgumentException
627
-     * @throws InvalidInterfaceException
628
-     * @throws InvalidDataTypeException
629
-     * @throws EE_Error
630
-     */
631
-    protected function _set_cached_property($field_name, $value, $cache_type = null)
632
-    {
633
-        // first make sure this property exists
634
-        $this->_model->field_settings_for($field_name);
635
-        $cache_type                                             = empty($cache_type) ? 'standard' : $cache_type;
636
-        $this->_cached_properties[ $field_name ][ $cache_type ] = $value;
637
-    }
638
-
639
-
640
-    /**
641
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
642
-     * This also SETS the cache if we return the actual property!
643
-     *
644
-     * @param string $field_name       the name of the property we're trying to retrieve
645
-     * @param bool   $pretty
646
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
647
-     *                                 (in cases where the same property may be used for different outputs
648
-     *                                 - i.e. datetime, money etc.)
649
-     *                                 It can also accept certain pre-defined "schema" strings
650
-     *                                 to define how to output the property.
651
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
652
-     * @return mixed                   whatever the value for the property is we're retrieving
653
-     * @throws InvalidArgumentException
654
-     * @throws InvalidInterfaceException
655
-     * @throws InvalidDataTypeException
656
-     * @throws EE_Error
657
-     */
658
-    protected function _get_cached_property($field_name, $pretty = false, $extra_cache_ref = null)
659
-    {
660
-        // verify the field exists
661
-        $this->_model->field_settings_for($field_name);
662
-        $cache_type = $pretty ? 'pretty' : 'standard';
663
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
664
-        if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
665
-            return $this->_cached_properties[ $field_name ][ $cache_type ];
666
-        }
667
-        $value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
668
-        $this->_set_cached_property($field_name, $value, $cache_type);
669
-        return $value;
670
-    }
671
-
672
-
673
-    /**
674
-     * If the cache didn't fetch the needed item, this fetches it.
675
-     *
676
-     * @param string $field_name
677
-     * @param bool   $pretty
678
-     * @param string $extra_cache_ref
679
-     * @return mixed
680
-     * @throws InvalidArgumentException
681
-     * @throws InvalidInterfaceException
682
-     * @throws InvalidDataTypeException
683
-     * @throws EE_Error
684
-     */
685
-    protected function _get_fresh_property($field_name, $pretty = false, $extra_cache_ref = null)
686
-    {
687
-        $field_obj = $this->_model->field_settings_for($field_name);
688
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
689
-        if ($field_obj instanceof EE_Datetime_Field) {
690
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
691
-        }
692
-        if (! isset($this->_fields[ $field_name ])) {
693
-            $this->_fields[ $field_name ] = null;
694
-        }
695
-        return $pretty
696
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
697
-            : $field_obj->prepare_for_get($this->_fields[ $field_name ]);
698
-    }
699
-
700
-
701
-    /**
702
-     * set timezone, formats, and output for EE_Datetime_Field objects
703
-     *
704
-     * @param EE_Datetime_Field $datetime_field
705
-     * @param bool              $pretty
706
-     * @param null              $date_or_time
707
-     * @return void
708
-     * @throws InvalidArgumentException
709
-     * @throws InvalidInterfaceException
710
-     * @throws InvalidDataTypeException
711
-     */
712
-    protected function _prepare_datetime_field(
713
-        EE_Datetime_Field $datetime_field,
714
-        $pretty = false,
715
-        $date_or_time = null
716
-    ) {
717
-        $datetime_field->set_timezone($this->_timezone);
718
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
719
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
720
-        // set the output returned
721
-        switch ($date_or_time) {
722
-            case 'D':
723
-                $datetime_field->set_date_time_output('date');
724
-                break;
725
-            case 'T':
726
-                $datetime_field->set_date_time_output('time');
727
-                break;
728
-            default:
729
-                $datetime_field->set_date_time_output();
730
-        }
731
-    }
732
-
733
-
734
-    /**
735
-     * This just takes care of clearing out the cached_properties
736
-     *
737
-     * @return void
738
-     */
739
-    protected function _clear_cached_properties()
740
-    {
741
-        $this->_cached_properties = [];
742
-    }
743
-
744
-
745
-    /**
746
-     * This just clears out ONE property if it exists in the cache
747
-     *
748
-     * @param string $property_name the property to remove if it exists (from the _cached_properties array)
749
-     * @return void
750
-     */
751
-    protected function _clear_cached_property($property_name)
752
-    {
753
-        if (isset($this->_cached_properties[ $property_name ])) {
754
-            unset($this->_cached_properties[ $property_name ]);
755
-        }
756
-    }
757
-
758
-
759
-    /**
760
-     * Ensures that this related thing is a model object.
761
-     *
762
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
763
-     * @param string $model_name   name of the related thing, eg 'Attendee',
764
-     * @return EE_Base_Class
765
-     * @throws ReflectionException
766
-     * @throws InvalidArgumentException
767
-     * @throws InvalidInterfaceException
768
-     * @throws InvalidDataTypeException
769
-     * @throws EE_Error
770
-     */
771
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
772
-    {
773
-        $other_model_instance = self::_get_model_instance_with_name(
774
-            self::_get_model_classname($model_name),
775
-            $this->_timezone
776
-        );
777
-        return $other_model_instance->ensure_is_obj($object_or_id);
778
-    }
779
-
780
-
781
-    /**
782
-     * Forgets the cached model of the given relation Name. So the next time we request it,
783
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
784
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
785
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
786
-     *
787
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
788
-     *                                                     Eg 'Registration'
789
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
790
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
791
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
792
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
793
-     *                                                     this is HasMany or HABTM.
794
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
795
-     *                                                     relation from all
796
-     * @throws InvalidArgumentException
797
-     * @throws InvalidInterfaceException
798
-     * @throws InvalidDataTypeException
799
-     * @throws EE_Error
800
-     * @throws ReflectionException
801
-     */
802
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
803
-    {
804
-        $relationship_to_model = $this->_model->related_settings_for($relationName);
805
-        $index_in_cache        = '';
806
-        if (! $relationship_to_model) {
807
-            throw new EE_Error(
808
-                sprintf(
809
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
810
-                    $relationName,
811
-                    get_class($this)
812
-                )
813
-            );
814
-        }
815
-        if ($clear_all) {
816
-            $obj_removed                             = true;
817
-            $this->_model_relations[ $relationName ] = null;
818
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
819
-            $obj_removed                             = $this->_model_relations[ $relationName ];
820
-            $this->_model_relations[ $relationName ] = null;
821
-        } else {
822
-            if (
823
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
824
-                && $object_to_remove_or_index_into_array->ID()
825
-            ) {
826
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
827
-                if (
828
-                    is_array($this->_model_relations[ $relationName ])
829
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
830
-                ) {
831
-                    $index_found_at = null;
832
-                    // find this object in the array even though it has a different key
833
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
834
-                        /** @noinspection TypeUnsafeComparisonInspection */
835
-                        if (
836
-                            $obj instanceof EE_Base_Class
837
-                            && (
838
-                                $obj == $object_to_remove_or_index_into_array
839
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
840
-                            )
841
-                        ) {
842
-                            $index_found_at = $index;
843
-                            break;
844
-                        }
845
-                    }
846
-                    if ($index_found_at) {
847
-                        $index_in_cache = $index_found_at;
848
-                    } else {
849
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
850
-                        // if it wasn't in it to begin with. So we're done
851
-                        return $object_to_remove_or_index_into_array;
852
-                    }
853
-                }
854
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
855
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
856
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
857
-                    /** @noinspection TypeUnsafeComparisonInspection */
858
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
859
-                        $index_in_cache = $index;
860
-                    }
861
-                }
862
-            } else {
863
-                $index_in_cache = $object_to_remove_or_index_into_array;
864
-            }
865
-            // supposedly we've found it. But it could just be that the client code
866
-            // provided a bad index/object
867
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
868
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
869
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
870
-            } else {
871
-                // that thing was never cached anyways.
872
-                $obj_removed = null;
873
-            }
874
-        }
875
-        return $obj_removed;
876
-    }
877
-
878
-
879
-    /**
880
-     * update_cache_after_object_save
881
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
882
-     * obtained after being saved to the db
883
-     *
884
-     * @param string        $relationName       - the type of object that is cached
885
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
886
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
887
-     * @return boolean TRUE on success, FALSE on fail
888
-     * @throws InvalidArgumentException
889
-     * @throws InvalidInterfaceException
890
-     * @throws InvalidDataTypeException
891
-     * @throws EE_Error
892
-     */
893
-    public function update_cache_after_object_save(
894
-        $relationName,
895
-        EE_Base_Class $newly_saved_object,
896
-        $current_cache_id = ''
897
-    ) {
898
-        // verify that incoming object is of the correct type
899
-        $obj_class = 'EE_' . $relationName;
900
-        if ($newly_saved_object instanceof $obj_class) {
901
-            /* @type EE_Base_Class $newly_saved_object */
902
-            // now get the type of relation
903
-            $relationship_to_model = $this->_model->related_settings_for($relationName);
904
-            // if this is a 1:1 relationship
905
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
906
-                // then just replace the cached object with the newly saved object
907
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
908
-                return true;
909
-                // or if it's some kind of sordid feral polyamorous relationship...
910
-            }
911
-            if (
912
-                is_array($this->_model_relations[ $relationName ])
913
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
914
-            ) {
915
-                // then remove the current cached item
916
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
917
-                // and cache the newly saved object using it's new ID
918
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
919
-                return true;
920
-            }
921
-        }
922
-        return false;
923
-    }
924
-
925
-
926
-    /**
927
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
928
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
929
-     *
930
-     * @param string $relationName
931
-     * @return EE_Base_Class
932
-     */
933
-    public function get_one_from_cache($relationName)
934
-    {
935
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
936
-            ? $this->_model_relations[ $relationName ]
937
-            : null;
938
-        if (is_array($cached_array_or_object)) {
939
-            return array_shift($cached_array_or_object);
940
-        }
941
-        return $cached_array_or_object;
942
-    }
943
-
944
-
945
-    /**
946
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
947
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
948
-     *
949
-     * @param string $relationName
950
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
951
-     * @throws InvalidArgumentException
952
-     * @throws InvalidInterfaceException
953
-     * @throws InvalidDataTypeException
954
-     * @throws EE_Error
955
-     * @throws ReflectionException
956
-     */
957
-    public function get_all_from_cache($relationName)
958
-    {
959
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
960
-        // if the result is not an array, but exists, make it an array
961
-        $objects = is_array($objects) ? $objects : [$objects];
962
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
963
-        // basically, if this model object was stored in the session, and these cached model objects
964
-        // already have IDs, let's make sure they're in their model's entity mapper
965
-        // otherwise we will have duplicates next time we call
966
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
967
-        $related_model = EE_Registry::instance()->load_model($relationName);
968
-        foreach ($objects as $model_object) {
969
-            if ($related_model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
970
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
971
-                if ($model_object->ID()) {
972
-                    $related_model->add_to_entity_map($model_object);
973
-                }
974
-            } else {
975
-                throw new EE_Error(
976
-                    sprintf(
977
-                        esc_html__(
978
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
979
-                            'event_espresso'
980
-                        ),
981
-                        $relationName,
982
-                        gettype($model_object)
983
-                    )
984
-                );
985
-            }
986
-        }
987
-        return $objects;
988
-    }
989
-
990
-
991
-    /**
992
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
993
-     * matching the given query conditions.
994
-     *
995
-     * @param null  $field_to_order_by  What field is being used as the reference point.
996
-     * @param int   $limit              How many objects to return.
997
-     * @param array $query_params       Any additional conditions on the query.
998
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
999
-     *                                  you can indicate just the columns you want returned
1000
-     * @return array|EE_Base_Class[]
1001
-     * @throws ReflectionException
1002
-     * @throws InvalidArgumentException
1003
-     * @throws InvalidInterfaceException
1004
-     * @throws InvalidDataTypeException
1005
-     * @throws EE_Error
1006
-     */
1007
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1008
-    {
1009
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1010
-            ? $this->_model->get_primary_key_field()->get_name()
1011
-            : $field_to_order_by;
1012
-        $current_value = ! empty($field) ? $this->get($field) : null;
1013
-        if (empty($field) || empty($current_value)) {
1014
-            return [];
1015
-        }
1016
-        return $this->_model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1017
-    }
1018
-
1019
-
1020
-    /**
1021
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1022
-     * matching the given query conditions.
1023
-     *
1024
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1025
-     * @param int   $limit              How many objects to return.
1026
-     * @param array $query_params       Any additional conditions on the query.
1027
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1028
-     *                                  you can indicate just the columns you want returned
1029
-     * @return array|EE_Base_Class[]
1030
-     * @throws ReflectionException
1031
-     * @throws InvalidArgumentException
1032
-     * @throws InvalidInterfaceException
1033
-     * @throws InvalidDataTypeException
1034
-     * @throws EE_Error
1035
-     */
1036
-    public function previous_x(
1037
-        $field_to_order_by = null,
1038
-        $limit = 1,
1039
-        $query_params = [],
1040
-        $columns_to_select = null
1041
-    ) {
1042
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1043
-            ? $this->_model->get_primary_key_field()->get_name()
1044
-            : $field_to_order_by;
1045
-        $current_value = ! empty($field) ? $this->get($field) : null;
1046
-        if (empty($field) || empty($current_value)) {
1047
-            return [];
1048
-        }
1049
-        return $this->_model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1050
-    }
1051
-
1052
-
1053
-    /**
1054
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1055
-     * matching the given query conditions.
1056
-     *
1057
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1058
-     * @param array $query_params       Any additional conditions on the query.
1059
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1060
-     *                                  you can indicate just the columns you want returned
1061
-     * @return array|EE_Base_Class
1062
-     * @throws ReflectionException
1063
-     * @throws InvalidArgumentException
1064
-     * @throws InvalidInterfaceException
1065
-     * @throws InvalidDataTypeException
1066
-     * @throws EE_Error
1067
-     */
1068
-    public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1069
-    {
1070
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1071
-            ? $this->_model->get_primary_key_field()->get_name()
1072
-            : $field_to_order_by;
1073
-        $current_value = ! empty($field) ? $this->get($field) : null;
1074
-        if (empty($field) || empty($current_value)) {
1075
-            return [];
1076
-        }
1077
-        return $this->_model->next($current_value, $field, $query_params, $columns_to_select);
1078
-    }
1079
-
1080
-
1081
-    /**
1082
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1083
-     * matching the given query conditions.
1084
-     *
1085
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1086
-     * @param array $query_params       Any additional conditions on the query.
1087
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1088
-     *                                  you can indicate just the column you want returned
1089
-     * @return array|EE_Base_Class
1090
-     * @throws ReflectionException
1091
-     * @throws InvalidArgumentException
1092
-     * @throws InvalidInterfaceException
1093
-     * @throws InvalidDataTypeException
1094
-     * @throws EE_Error
1095
-     */
1096
-    public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1097
-    {
1098
-        $field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1099
-            ? $this->_model->get_primary_key_field()->get_name()
1100
-            : $field_to_order_by;
1101
-        $current_value = ! empty($field) ? $this->get($field) : null;
1102
-        if (empty($field) || empty($current_value)) {
1103
-            return [];
1104
-        }
1105
-        return $this->_model->previous($current_value, $field, $query_params, $columns_to_select);
1106
-    }
1107
-
1108
-
1109
-    /**
1110
-     * Overrides parent because parent expects old models.
1111
-     * This also doesn't do any validation, and won't work for serialized arrays
1112
-     *
1113
-     * @param string $field_name
1114
-     * @param mixed  $field_value_from_db
1115
-     * @throws InvalidArgumentException
1116
-     * @throws InvalidInterfaceException
1117
-     * @throws InvalidDataTypeException
1118
-     * @throws EE_Error
1119
-     */
1120
-    public function set_from_db($field_name, $field_value_from_db)
1121
-    {
1122
-        $field_obj = $this->_model->field_settings_for($field_name);
1123
-        if ($field_obj instanceof EE_Model_Field_Base) {
1124
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
1125
-            // eg, a CPT model object could have an entry in the posts table, but no
1126
-            // entry in the meta table. Meaning that all its columns in the meta table
1127
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
1128
-            if ($field_value_from_db === null) {
1129
-                if ($field_obj->is_nullable()) {
1130
-                    // if the field allows nulls, then let it be null
1131
-                    $field_value = null;
1132
-                } else {
1133
-                    $field_value = $field_obj->get_default_value();
1134
-                }
1135
-            } else {
1136
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1137
-            }
1138
-            $this->_fields[ $field_name ] = $field_value;
1139
-            $this->_clear_cached_property($field_name);
1140
-        }
1141
-    }
1142
-
1143
-
1144
-    /**
1145
-     * verifies that the specified field is of the correct type
1146
-     *
1147
-     * @param string $field_name
1148
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1149
-     *                                (in cases where the same property may be used for different outputs
1150
-     *                                - i.e. datetime, money etc.)
1151
-     * @return mixed
1152
-     * @throws InvalidArgumentException
1153
-     * @throws InvalidInterfaceException
1154
-     * @throws InvalidDataTypeException
1155
-     * @throws EE_Error
1156
-     */
1157
-    public function get($field_name, $extra_cache_ref = null)
1158
-    {
1159
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1160
-    }
1161
-
1162
-
1163
-    /**
1164
-     * This method simply returns the RAW unprocessed value for the given property in this class
1165
-     *
1166
-     * @param string $field_name A valid field name
1167
-     * @return mixed              Whatever the raw value stored on the property is.
1168
-     * @throws InvalidArgumentException
1169
-     * @throws InvalidInterfaceException
1170
-     * @throws InvalidDataTypeException
1171
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1172
-     */
1173
-    public function get_raw($field_name)
1174
-    {
1175
-        $field_settings = $this->_model->field_settings_for($field_name);
1176
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1177
-            ? $this->_fields[ $field_name ]->format('U')
1178
-            : $this->_fields[ $field_name ];
1179
-    }
1180
-
1181
-
1182
-    /**
1183
-     * This is used to return the internal DateTime object used for a field that is a
1184
-     * EE_Datetime_Field.
1185
-     *
1186
-     * @param string $field_name               The field name retrieving the DateTime object.
1187
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1188
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1189
-     *                                         EE_Datetime_Field and but the field value is null, then
1190
-     *                                         just null is returned (because that indicates that likely
1191
-     *                                         this field is nullable).
1192
-     * @throws InvalidArgumentException
1193
-     * @throws InvalidDataTypeException
1194
-     * @throws InvalidInterfaceException
1195
-     */
1196
-    public function get_DateTime_object($field_name)
1197
-    {
1198
-        $field_settings = $this->_model->field_settings_for($field_name);
1199
-        if (! $field_settings instanceof EE_Datetime_Field) {
1200
-            EE_Error::add_error(
1201
-                sprintf(
1202
-                    esc_html__(
1203
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1204
-                        'event_espresso'
1205
-                    ),
1206
-                    $field_name
1207
-                ),
1208
-                __FILE__,
1209
-                __FUNCTION__,
1210
-                __LINE__
1211
-            );
1212
-            return false;
1213
-        }
1214
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1215
-            ? clone $this->_fields[ $field_name ]
1216
-            : null;
1217
-    }
1218
-
1219
-
1220
-    /**
1221
-     * To be used in template to immediately echo out the value, and format it for output.
1222
-     * Eg, should call stripslashes and whatnot before echoing
1223
-     *
1224
-     * @param string $field_name      the name of the field as it appears in the DB
1225
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1226
-     *                                (in cases where the same property may be used for different outputs
1227
-     *                                - i.e. datetime, money etc.)
1228
-     * @return void
1229
-     * @throws InvalidArgumentException
1230
-     * @throws InvalidInterfaceException
1231
-     * @throws InvalidDataTypeException
1232
-     * @throws EE_Error
1233
-     */
1234
-    public function e($field_name, $extra_cache_ref = null)
1235
-    {
1236
-        echo $this->get_pretty($field_name, $extra_cache_ref);
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1242
-     * can be easily used as the value of form input.
1243
-     *
1244
-     * @param string $field_name
1245
-     * @return void
1246
-     * @throws InvalidArgumentException
1247
-     * @throws InvalidInterfaceException
1248
-     * @throws InvalidDataTypeException
1249
-     * @throws EE_Error
1250
-     */
1251
-    public function f($field_name)
1252
-    {
1253
-        $this->e($field_name, 'form_input');
1254
-    }
1255
-
1256
-
1257
-    /**
1258
-     * Same as `f()` but just returns the value instead of echoing it
1259
-     *
1260
-     * @param string $field_name
1261
-     * @return string
1262
-     * @throws InvalidArgumentException
1263
-     * @throws InvalidInterfaceException
1264
-     * @throws InvalidDataTypeException
1265
-     * @throws EE_Error
1266
-     */
1267
-    public function get_f($field_name)
1268
-    {
1269
-        return (string)$this->get_pretty($field_name, 'form_input');
1270
-    }
1271
-
1272
-
1273
-    /**
1274
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1275
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1276
-     * to see what options are available.
1277
-     *
1278
-     * @param string $field_name
1279
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1280
-     *                                (in cases where the same property may be used for different outputs
1281
-     *                                - i.e. datetime, money etc.)
1282
-     * @return mixed
1283
-     * @throws InvalidArgumentException
1284
-     * @throws InvalidInterfaceException
1285
-     * @throws InvalidDataTypeException
1286
-     * @throws EE_Error
1287
-     */
1288
-    public function get_pretty($field_name, $extra_cache_ref = null)
1289
-    {
1290
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1291
-    }
1292
-
1293
-
1294
-    /**
1295
-     * This simply returns the datetime for the given field name
1296
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1297
-     * (and the equivalent e_date, e_time, e_datetime).
1298
-     *
1299
-     * @access   protected
1300
-     * @param string  $field_name    Field on the instantiated EE_Base_Class child object
1301
-     * @param string  $date_format   valid datetime format used for date
1302
-     *                               (if '' then we just use the default on the field,
1303
-     *                               if NULL we use the last-used format)
1304
-     * @param string  $time_format   Same as above except this is for time format
1305
-     * @param string  $date_or_time  if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1306
-     * @param boolean $echo          Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1307
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1308
-     *                               if field is not a valid dtt field, or void if echoing
1309
-     * @throws InvalidArgumentException
1310
-     * @throws InvalidInterfaceException
1311
-     * @throws InvalidDataTypeException
1312
-     * @throws EE_Error
1313
-     */
1314
-    protected function _get_datetime(
1315
-        $field_name,
1316
-        $date_format = '',
1317
-        $time_format = '',
1318
-        $date_or_time = '',
1319
-        $echo = false
1320
-    ) {
1321
-        // clear cached property
1322
-        $this->_clear_cached_property($field_name);
1323
-        // reset format properties because they are used in get()
1324
-        $this->_dt_frmt = $date_format !== '' ? $date_format : $this->_dt_frmt;
1325
-        $this->_tm_frmt = $time_format !== '' ? $time_format : $this->_tm_frmt;
1326
-        if ($echo) {
1327
-            $this->e($field_name, $date_or_time);
1328
-            return '';
1329
-        }
1330
-        return $this->get($field_name, $date_or_time);
1331
-    }
1332
-
1333
-
1334
-    /**
1335
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1336
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1337
-     * other echoes the pretty value for dtt)
1338
-     *
1339
-     * @param string $field_name name of model object datetime field holding the value
1340
-     * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1341
-     * @return string            datetime value formatted
1342
-     * @throws InvalidArgumentException
1343
-     * @throws InvalidInterfaceException
1344
-     * @throws InvalidDataTypeException
1345
-     * @throws EE_Error
1346
-     */
1347
-    public function get_date($field_name, $format = '')
1348
-    {
1349
-        return $this->_get_datetime($field_name, $format, null, 'D');
1350
-    }
1351
-
1352
-
1353
-    /**
1354
-     * @param        $field_name
1355
-     * @param string $format
1356
-     * @throws InvalidArgumentException
1357
-     * @throws InvalidInterfaceException
1358
-     * @throws InvalidDataTypeException
1359
-     * @throws EE_Error
1360
-     */
1361
-    public function e_date($field_name, $format = '')
1362
-    {
1363
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1364
-    }
1365
-
1366
-
1367
-    /**
1368
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1369
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1370
-     * other echoes the pretty value for dtt)
1371
-     *
1372
-     * @param string $field_name name of model object datetime field holding the value
1373
-     * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1374
-     * @return string             datetime value formatted
1375
-     * @throws InvalidArgumentException
1376
-     * @throws InvalidInterfaceException
1377
-     * @throws InvalidDataTypeException
1378
-     * @throws EE_Error
1379
-     */
1380
-    public function get_time($field_name, $format = '')
1381
-    {
1382
-        return $this->_get_datetime($field_name, null, $format, 'T');
1383
-    }
1384
-
1385
-
1386
-    /**
1387
-     * @param        $field_name
1388
-     * @param string $format
1389
-     * @throws InvalidArgumentException
1390
-     * @throws InvalidInterfaceException
1391
-     * @throws InvalidDataTypeException
1392
-     * @throws EE_Error
1393
-     */
1394
-    public function e_time($field_name, $format = '')
1395
-    {
1396
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1397
-    }
1398
-
1399
-
1400
-    /**
1401
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1402
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1403
-     * other echoes the pretty value for dtt)
1404
-     *
1405
-     * @param string $field_name  name of model object datetime field holding the value
1406
-     * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1407
-     * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1408
-     * @return string             datetime value formatted
1409
-     * @throws InvalidArgumentException
1410
-     * @throws InvalidInterfaceException
1411
-     * @throws InvalidDataTypeException
1412
-     * @throws EE_Error
1413
-     */
1414
-    public function get_datetime($field_name, $date_format = '', $time_format = '')
1415
-    {
1416
-        return $this->_get_datetime($field_name, $date_format, $time_format);
1417
-    }
1418
-
1419
-
1420
-    /**
1421
-     * @param string $field_name
1422
-     * @param string $date_format
1423
-     * @param string $time_format
1424
-     * @throws InvalidArgumentException
1425
-     * @throws InvalidInterfaceException
1426
-     * @throws InvalidDataTypeException
1427
-     * @throws EE_Error
1428
-     */
1429
-    public function e_datetime($field_name, $date_format = '', $time_format = '')
1430
-    {
1431
-        $this->_get_datetime($field_name, $date_format, $time_format, null, true);
1432
-    }
1433
-
1434
-
1435
-    /**
1436
-     * Get the i8ln value for a date using the WordPress @param string $field_name The EE_Datetime_Field reference for
1437
-     *                           the date being retrieved.
1438
-     *
1439
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1440
-     *                           on the object will be used.
1441
-     * @return string Date and time string in set locale or false if no field exists for the given
1442
-     * @throws InvalidArgumentException
1443
-     * @throws InvalidInterfaceException
1444
-     * @throws InvalidDataTypeException
1445
-     * @throws EE_Error
1446
-     *                           field name.
1447
-     * @see date_i18n function.
1448
-     *
1449
-     */
1450
-    public function get_i18n_datetime($field_name, $format = '')
1451
-    {
1452
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1453
-        return date_i18n(
1454
-            $format,
1455
-            EEH_DTT_Helper::get_timestamp_with_offset(
1456
-                $this->get_raw($field_name),
1457
-                $this->_timezone
1458
-            )
1459
-        );
1460
-    }
1461
-
1462
-
1463
-    /**
1464
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1465
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1466
-     * thrown.
1467
-     *
1468
-     * @param string $field_name The field name being checked
1469
-     * @return EE_Datetime_Field
1470
-     * @throws InvalidArgumentException
1471
-     * @throws InvalidInterfaceException
1472
-     * @throws InvalidDataTypeException
1473
-     * @throws EE_Error
1474
-     */
1475
-    protected function _get_dtt_field_settings($field_name)
1476
-    {
1477
-        $field = $this->_model->field_settings_for($field_name);
1478
-        // check if field is dtt
1479
-        if ($field instanceof EE_Datetime_Field) {
1480
-            return $field;
1481
-        }
1482
-        throw new EE_Error(
1483
-            sprintf(
1484
-                esc_html__(
1485
-                    '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',
1486
-                    'event_espresso'
1487
-                ),
1488
-                $field_name,
1489
-                self::_get_model_classname(get_class($this))
1490
-            )
1491
-        );
1492
-    }
1493
-
1494
-
1495
-
1496
-
1497
-    /**
1498
-     * NOTE ABOUT BELOW:
1499
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1500
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1501
-     * you want to set both date and time at the same time, you can just use the models default set($field_name,$value)
1502
-     * method and make sure you send the entire datetime value for setting.
1503
-     */
1504
-    /**
1505
-     * sets the time on a datetime property
1506
-     *
1507
-     * @access protected
1508
-     * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
1509
-     * @param string          $field_name the name of the field the time is being set on (must match a
1510
-     *                                    EE_Datetime_Field)
1511
-     * @throws InvalidArgumentException
1512
-     * @throws InvalidInterfaceException
1513
-     * @throws InvalidDataTypeException
1514
-     * @throws EE_Error
1515
-     */
1516
-    protected function _set_time_for($time, $field_name)
1517
-    {
1518
-        $this->_set_date_time('T', $time, $field_name);
1519
-    }
1520
-
1521
-
1522
-    /**
1523
-     * sets the date on a datetime property
1524
-     *
1525
-     * @access protected
1526
-     * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
1527
-     * @param string          $field_name the name of the field the date is being set on (must match a
1528
-     *                                    EE_Datetime_Field)
1529
-     * @throws InvalidArgumentException
1530
-     * @throws InvalidInterfaceException
1531
-     * @throws InvalidDataTypeException
1532
-     * @throws EE_Error
1533
-     */
1534
-    protected function _set_date_for($date, $field_name)
1535
-    {
1536
-        $this->_set_date_time('D', $date, $field_name);
1537
-    }
1538
-
1539
-
1540
-    /**
1541
-     * This takes care of setting a date or time independently on a given model object property. This method also
1542
-     * verifies that the given field name matches a model object property and is for a EE_Datetime_Field field
1543
-     *
1544
-     * @access protected
1545
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1546
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1547
-     * @param string          $field_name     the name of the field the date OR time is being set on (must match a
1548
-     *                                        EE_Datetime_Field property)
1549
-     * @throws InvalidArgumentException
1550
-     * @throws InvalidInterfaceException
1551
-     * @throws InvalidDataTypeException
1552
-     * @throws EE_Error
1553
-     */
1554
-    protected function _set_date_time($what, $datetime_value, $field_name)
1555
-    {
1556
-        $field = $this->_get_dtt_field_settings($field_name);
1557
-        $field->set_timezone($this->_timezone);
1558
-        $field->set_date_format($this->_dt_frmt);
1559
-        $field->set_time_format($this->_tm_frmt);
1560
-        $what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
1561
-        switch ($what) {
1562
-            case 'T':
1563
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
1564
-                    $datetime_value,
1565
-                    $this->_fields[ $field_name ]
1566
-                );
1567
-                $this->_has_changes           = true;
1568
-                break;
1569
-            case 'D':
1570
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
1571
-                    $datetime_value,
1572
-                    $this->_fields[ $field_name ]
1573
-                );
1574
-                $this->_has_changes           = true;
1575
-                break;
1576
-            case 'B':
1577
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
1578
-                $this->_has_changes           = true;
1579
-                break;
1580
-        }
1581
-        $this->_clear_cached_property($field_name);
1582
-    }
1583
-
1584
-
1585
-    /**
1586
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1587
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1588
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1589
-     * that could lead to some unexpected results!
1590
-     *
1591
-     * @access public
1592
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1593
-     *                                         value being returned.
1594
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1595
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1596
-     * @param string $prepend                  You can include something to prepend on the timestamp
1597
-     * @param string $append                   You can include something to append on the timestamp
1598
-     * @return string timestamp
1599
-     * @throws InvalidArgumentException
1600
-     * @throws InvalidInterfaceException
1601
-     * @throws InvalidDataTypeException
1602
-     * @throws EE_Error
1603
-     */
1604
-    public function display_in_my_timezone(
1605
-        $field_name,
1606
-        $callback = 'get_datetime',
1607
-        $args = null,
1608
-        $prepend = '',
1609
-        $append = ''
1610
-    ) {
1611
-        $timezone = EEH_DTT_Helper::get_timezone();
1612
-        if ($timezone === $this->_timezone) {
1613
-            return '';
1614
-        }
1615
-        $original_timezone = $this->_timezone;
1616
-        $this->set_timezone($timezone);
1617
-        $fn   = (array)$field_name;
1618
-        $args = array_merge($fn, (array)$args);
1619
-        if (! method_exists($this, $callback)) {
1620
-            throw new EE_Error(
1621
-                sprintf(
1622
-                    esc_html__(
1623
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1624
-                        'event_espresso'
1625
-                    ),
1626
-                    $callback
1627
-                )
1628
-            );
1629
-        }
1630
-        $args   = (array)$args;
1631
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
1632
-        $this->set_timezone($original_timezone);
1633
-        return $return;
1634
-    }
1635
-
1636
-
1637
-    /**
1638
-     * Deletes this model object.
1639
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1640
-     * override
1641
-     * `EE_Base_Class::_delete` NOT this class.
1642
-     *
1643
-     * @return boolean | int
1644
-     * @throws ReflectionException
1645
-     * @throws InvalidArgumentException
1646
-     * @throws InvalidInterfaceException
1647
-     * @throws InvalidDataTypeException
1648
-     * @throws EE_Error
1649
-     */
1650
-    public function delete()
1651
-    {
1652
-        /**
1653
-         * Called just before the `EE_Base_Class::_delete` method call.
1654
-         * Note:
1655
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1656
-         * should be aware that `_delete` may not always result in a permanent delete.
1657
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1658
-         * soft deletes (trash) the object and does not permanently delete it.
1659
-         *
1660
-         * @param EE_Base_Class $model_object about to be 'deleted'
1661
-         */
1662
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1663
-        $result = $this->_delete();
1664
-        /**
1665
-         * Called just after the `EE_Base_Class::_delete` method call.
1666
-         * Note:
1667
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1668
-         * should be aware that `_delete` may not always result in a permanent delete.
1669
-         * For example `EE_Soft_Base_Class::_delete`
1670
-         * soft deletes (trash) the object and does not permanently delete it.
1671
-         *
1672
-         * @param EE_Base_Class $model_object that was just 'deleted'
1673
-         * @param boolean       $result
1674
-         */
1675
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1676
-        return $result;
1677
-    }
1678
-
1679
-
1680
-    /**
1681
-     * Calls the specific delete method for the instantiated class.
1682
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1683
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1684
-     * `EE_Base_Class::delete`
1685
-     *
1686
-     * @return bool|int
1687
-     * @throws ReflectionException
1688
-     * @throws InvalidArgumentException
1689
-     * @throws InvalidInterfaceException
1690
-     * @throws InvalidDataTypeException
1691
-     * @throws EE_Error
1692
-     */
1693
-    protected function _delete()
1694
-    {
1695
-        return $this->delete_permanently();
1696
-    }
1697
-
1698
-
1699
-    /**
1700
-     * Deletes this model object permanently from db
1701
-     * (but keep in mind related models may block the delete and return an error)
1702
-     *
1703
-     * @return bool | int
1704
-     * @throws ReflectionException
1705
-     * @throws InvalidArgumentException
1706
-     * @throws InvalidInterfaceException
1707
-     * @throws InvalidDataTypeException
1708
-     * @throws EE_Error
1709
-     */
1710
-    public function delete_permanently()
1711
-    {
1712
-        /**
1713
-         * Called just before HARD deleting a model object
1714
-         *
1715
-         * @param EE_Base_Class $model_object about to be 'deleted'
1716
-         */
1717
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1718
-        $result = $this->_model->delete_permanently_by_ID($this->ID());
1719
-        $this->refresh_cache_of_related_objects();
1720
-        /**
1721
-         * Called just after HARD deleting a model object
1722
-         *
1723
-         * @param EE_Base_Class $model_object that was just 'deleted'
1724
-         * @param boolean       $result
1725
-         */
1726
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1727
-        return $result;
1728
-    }
1729
-
1730
-
1731
-    /**
1732
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1733
-     * related model objects
1734
-     *
1735
-     * @throws ReflectionException
1736
-     * @throws InvalidArgumentException
1737
-     * @throws InvalidInterfaceException
1738
-     * @throws InvalidDataTypeException
1739
-     * @throws EE_Error
1740
-     */
1741
-    public function refresh_cache_of_related_objects()
1742
-    {
1743
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1744
-            if (! empty($this->_model_relations[ $relation_name ])) {
1745
-                $related_objects = $this->_model_relations[ $relation_name ];
1746
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1747
-                    // this relation only stores a single model object, not an array
1748
-                    // but let's make it consistent
1749
-                    $related_objects = [$related_objects];
1750
-                }
1751
-                foreach ($related_objects as $related_object) {
1752
-                    // only refresh their cache if they're in memory
1753
-                    if ($related_object instanceof EE_Base_Class) {
1754
-                        $related_object->clear_cache(
1755
-                            $this->_model->get_this_model_name(),
1756
-                            $this
1757
-                        );
1758
-                    }
1759
-                }
1760
-            }
1761
-        }
1762
-    }
1763
-
1764
-
1765
-    /**
1766
-     *        Saves this object to the database. An array may be supplied to set some values on this
1767
-     * object just before saving.
1768
-     *
1769
-     * @access public
1770
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1771
-     *                                 if provided during the save() method (often client code will change the fields'
1772
-     *                                 values before calling save)
1773
-     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1774
-     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1775
-     * @throws InvalidInterfaceException
1776
-     * @throws InvalidDataTypeException
1777
-     * @throws EE_Error
1778
-     * @throws InvalidArgumentException
1779
-     * @throws ReflectionException
1780
-     * @throws ReflectionException
1781
-     * @throws ReflectionException
1782
-     */
1783
-    public function save($set_cols_n_values = [])
1784
-    {
1785
-        /**
1786
-         * Filters the fields we're about to save on the model object
1787
-         *
1788
-         * @param array         $set_cols_n_values
1789
-         * @param EE_Base_Class $model_object
1790
-         */
1791
-        $set_cols_n_values = (array)apply_filters(
1792
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1793
-            $set_cols_n_values,
1794
-            $this
1795
-        );
1796
-        // set attributes as provided in $set_cols_n_values
1797
-        foreach ($set_cols_n_values as $column => $value) {
1798
-            $this->set($column, $value);
1799
-        }
1800
-        // no changes ? then don't do anything
1801
-        if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
1802
-            return 0;
1803
-        }
1804
-        /**
1805
-         * Saving a model object.
1806
-         * Before we perform a save, this action is fired.
1807
-         *
1808
-         * @param EE_Base_Class $model_object the model object about to be saved.
1809
-         */
1810
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1811
-        if (! $this->allow_persist()) {
1812
-            return 0;
1813
-        }
1814
-        // now get current attribute values
1815
-        $save_cols_n_values = $this->_fields;
1816
-        // if the object already has an ID, update it. Otherwise, insert it
1817
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1818
-        // They have been
1819
-        $old_assumption_concerning_value_preparation = $this->_model
1820
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1821
-        $this->_model->assume_values_already_prepared_by_model_object(true);
1822
-        // does this model have an autoincrement PK?
1823
-        if ($this->_model->has_primary_key_field()) {
1824
-            if ($this->_model->get_primary_key_field()->is_auto_increment()) {
1825
-                // ok check if it's set, if so: update; if not, insert
1826
-                if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
1827
-                    $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
1828
-                } else {
1829
-                    unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
1830
-                    $results = $this->_model->insert($save_cols_n_values);
1831
-                    if ($results) {
1832
-                        // if successful, set the primary key
1833
-                        // but don't use the normal SET method, because it will check if
1834
-                        // an item with the same ID exists in the mapper & db, then
1835
-                        // will find it in the db (because we just added it) and THAT object
1836
-                        // will get added to the mapper before we can add this one!
1837
-                        // but if we just avoid using the SET method, all that headache can be avoided
1838
-                        $pk_field_name                   = $this->_model->primary_key_name();
1839
-                        $this->_fields[ $pk_field_name ] = $results;
1840
-                        $this->_clear_cached_property($pk_field_name);
1841
-                        $this->_model->add_to_entity_map($this);
1842
-                        $this->_update_cached_related_model_objs_fks();
1843
-                    }
1844
-                }
1845
-            } else {// PK is NOT auto-increment
1846
-                // so check if one like it already exists in the db
1847
-                if ($this->_model->exists_by_ID($this->ID())) {
1848
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1849
-                        throw new EE_Error(
1850
-                            sprintf(
1851
-                                esc_html__(
1852
-                                    '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',
1853
-                                    'event_espresso'
1854
-                                ),
1855
-                                get_class($this),
1856
-                                get_class($this->_model) . '::instance()->add_to_entity_map()',
1857
-                                get_class($this->_model) . '::instance()->get_one_by_ID()',
1858
-                                '<br />'
1859
-                            )
1860
-                        );
1861
-                    }
1862
-                    $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
1863
-                } else {
1864
-                    $results = $this->_model->insert($save_cols_n_values);
1865
-                    $this->_update_cached_related_model_objs_fks();
1866
-                }
1867
-            }
1868
-        } else {// there is NO primary key
1869
-            $already_in_db = false;
1870
-            foreach ($this->_model->unique_indexes() as $index) {
1871
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1872
-                if ($this->_model->exists([$uniqueness_where_params])) {
1873
-                    $already_in_db = true;
1874
-                }
1875
-            }
1876
-            if ($already_in_db) {
1877
-                $combined_pk_fields_n_values = array_intersect_key(
1878
-                    $save_cols_n_values,
1879
-                    $this->_model->get_combined_primary_key_fields()
1880
-                );
1881
-                $results                     = $this->_model->update(
1882
-                    $save_cols_n_values,
1883
-                    $combined_pk_fields_n_values
1884
-                );
1885
-            } else {
1886
-                $results = $this->_model->insert($save_cols_n_values);
1887
-            }
1888
-        }
1889
-        // restore the old assumption about values being prepared by the model object
1890
-        $this->_model->assume_values_already_prepared_by_model_object(
1891
-            $old_assumption_concerning_value_preparation
1892
-        );
1893
-        /**
1894
-         * After saving the model object this action is called
1895
-         *
1896
-         * @param EE_Base_Class $model_object which was just saved
1897
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1898
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1899
-         */
1900
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1901
-        $this->_has_changes = false;
1902
-        return $results;
1903
-    }
1904
-
1905
-
1906
-    /**
1907
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1908
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1909
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1910
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1911
-     * 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
1912
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1913
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1914
-     *
1915
-     * @return void
1916
-     * @throws ReflectionException
1917
-     * @throws InvalidArgumentException
1918
-     * @throws InvalidInterfaceException
1919
-     * @throws InvalidDataTypeException
1920
-     * @throws EE_Error
1921
-     */
1922
-    protected function _update_cached_related_model_objs_fks()
1923
-    {
1924
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1925
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1926
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1927
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1928
-                        $this->_model->get_this_model_name()
1929
-                    );
1930
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1931
-                    if ($related_model_obj_in_cache->ID()) {
1932
-                        $related_model_obj_in_cache->save();
1933
-                    }
1934
-                }
1935
-            }
1936
-        }
1937
-    }
1938
-
1939
-
1940
-    /**
1941
-     * Saves this model object and its NEW cached relations to the database.
1942
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1943
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1944
-     * because otherwise, there's a potential for infinite looping of saving
1945
-     * Saves the cached related model objects, and ensures the relation between them
1946
-     * and this object and properly setup
1947
-     *
1948
-     * @return int ID of new model object on save; 0 on failure+
1949
-     * @throws ReflectionException
1950
-     * @throws InvalidArgumentException
1951
-     * @throws InvalidInterfaceException
1952
-     * @throws InvalidDataTypeException
1953
-     * @throws EE_Error
1954
-     */
1955
-    public function save_new_cached_related_model_objs()
1956
-    {
1957
-        // make sure this has been saved
1958
-        if (! $this->ID()) {
1959
-            $id = $this->save();
1960
-        } else {
1961
-            $id = $this->ID();
1962
-        }
1963
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
1964
-        foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
1965
-            if ($this->_model_relations[ $relationName ]) {
1966
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1967
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1968
-                /* @var $related_model_obj EE_Base_Class */
1969
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
1970
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
1971
-                    // but ONLY if it DOES NOT exist in the DB
1972
-                    $related_model_obj = $this->_model_relations[ $relationName ];
1973
-                    // if( ! $related_model_obj->ID()){
1974
-                    $this->_add_relation_to($related_model_obj, $relationName);
1975
-                    $related_model_obj->save_new_cached_related_model_objs();
1976
-                    // }
1977
-                } else {
1978
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
1979
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
1980
-                        // but ONLY if it DOES NOT exist in the DB
1981
-                        // if( ! $related_model_obj->ID()){
1982
-                        $this->_add_relation_to($related_model_obj, $relationName);
1983
-                        $related_model_obj->save_new_cached_related_model_objs();
1984
-                        // }
1985
-                    }
1986
-                }
1987
-            }
1988
-        }
1989
-        return $id;
1990
-    }
1991
-
1992
-
1993
-    /**
1994
-     * for getting a model while instantiated.
1995
-     *
1996
-     * @return EEM_Base | EEM_CPT_Base
1997
-     * @throws ReflectionException
1998
-     * @throws InvalidArgumentException
1999
-     * @throws InvalidInterfaceException
2000
-     * @throws InvalidDataTypeException
2001
-     * @throws EE_Error
2002
-     */
2003
-    public function get_model()
2004
-    {
2005
-        if (! $this->_model) {
2006
-            $modelName    = self::_get_model_classname(get_class($this));
2007
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2008
-        }
2009
-        return $this->_model;
2010
-    }
2011
-
2012
-
2013
-    /**
2014
-     * @param $props_n_values
2015
-     * @param $classname
2016
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2017
-     * @throws ReflectionException
2018
-     * @throws InvalidArgumentException
2019
-     * @throws InvalidInterfaceException
2020
-     * @throws InvalidDataTypeException
2021
-     * @throws EE_Error
2022
-     */
2023
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2024
-    {
2025
-        // TODO: will not work for Term_Relationships because they have no PK!
2026
-        $primary_id_ref = self::_get_primary_key_name($classname);
2027
-        if (
2028
-            array_key_exists($primary_id_ref, $props_n_values)
2029
-            && ! empty($props_n_values[ $primary_id_ref ])
2030
-        ) {
2031
-            $id = $props_n_values[ $primary_id_ref ];
2032
-            return self::_get_model($classname)->get_from_entity_map($id);
2033
-        }
2034
-        return false;
2035
-    }
2036
-
2037
-
2038
-    /**
2039
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2040
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2041
-     * 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
2042
-     * we return false.
2043
-     *
2044
-     * @param array  $props_n_values    incoming array of properties and their values
2045
-     * @param string $classname         the classname of the child class
2046
-     * @param null   $timezone
2047
-     * @param array  $date_formats      incoming date_formats in an array where the first value is the
2048
-     *                                  date_format and the second value is the time format
2049
-     * @return mixed (EE_Base_Class|bool)
2050
-     * @throws InvalidArgumentException
2051
-     * @throws InvalidInterfaceException
2052
-     * @throws InvalidDataTypeException
2053
-     * @throws EE_Error
2054
-     * @throws ReflectionException
2055
-     * @throws ReflectionException
2056
-     * @throws ReflectionException
2057
-     */
2058
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = [])
2059
-    {
2060
-        $existing = null;
2061
-        $model    = self::_get_model($classname, $timezone);
2062
-        if ($model->has_primary_key_field()) {
2063
-            $primary_id_ref = self::_get_primary_key_name($classname);
2064
-            if (
2065
-                array_key_exists($primary_id_ref, $props_n_values)
2066
-                && ! empty($props_n_values[ $primary_id_ref ])
2067
-            ) {
2068
-                $existing = $model->get_one_by_ID(
2069
-                    $props_n_values[ $primary_id_ref ]
2070
-                );
2071
-            }
2072
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2073
-            // no primary key on this model, but there's still a matching item in the DB
2074
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2075
-                self::_get_model($classname, $timezone)
2076
-                    ->get_index_primary_key_string($props_n_values)
2077
-            );
2078
-        }
2079
-        if ($existing) {
2080
-            // set date formats if present before setting values
2081
-            if (! empty($date_formats) && is_array($date_formats)) {
2082
-                $existing->set_date_format($date_formats[0]);
2083
-                $existing->set_time_format($date_formats[1]);
2084
-            } else {
2085
-                // set default formats for date and time
2086
-                $existing->set_date_format(get_option('date_format'));
2087
-                $existing->set_time_format(get_option('time_format'));
2088
-            }
2089
-            foreach ($props_n_values as $property => $field_value) {
2090
-                $existing->set($property, $field_value);
2091
-            }
2092
-            return $existing;
2093
-        }
2094
-        return false;
2095
-    }
2096
-
2097
-
2098
-    /**
2099
-     * Gets the EEM_*_Model for this class
2100
-     *
2101
-     * @access public now, as this is more convenient
2102
-     * @param      $classname
2103
-     * @param null $timezone
2104
-     * @return EEM_Base
2105
-     * @throws InvalidArgumentException
2106
-     * @throws InvalidInterfaceException
2107
-     * @throws InvalidDataTypeException
2108
-     * @throws EE_Error
2109
-     * @throws ReflectionException
2110
-     */
2111
-    protected static function _get_model($classname, $timezone = null)
2112
-    {
2113
-        // find model for this class
2114
-        if (! $classname) {
2115
-            throw new EE_Error(
2116
-                sprintf(
2117
-                    esc_html__(
2118
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2119
-                        'event_espresso'
2120
-                    ),
2121
-                    $classname
2122
-                )
2123
-            );
2124
-        }
2125
-        $modelName = self::_get_model_classname($classname);
2126
-        return self::_get_model_instance_with_name($modelName, $timezone);
2127
-    }
2128
-
2129
-
2130
-    /**
2131
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2132
-     *
2133
-     * @param string $model_classname
2134
-     * @param null   $timezone
2135
-     * @return EEM_Base
2136
-     * @throws ReflectionException
2137
-     * @throws InvalidArgumentException
2138
-     * @throws InvalidInterfaceException
2139
-     * @throws InvalidDataTypeException
2140
-     * @throws EE_Error
2141
-     */
2142
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2143
-    {
2144
-        $model_classname = str_replace('EEM_', '', $model_classname);
2145
-        $model           = EE_Registry::instance()->load_model($model_classname);
2146
-        $model->set_timezone($timezone);
2147
-        return $model;
2148
-    }
2149
-
2150
-
2151
-    /**
2152
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2153
-     * Also works if a model class's classname is provided (eg EE_Registration).
2154
-     *
2155
-     * @param null $model_name
2156
-     * @return string like EEM_Attendee
2157
-     */
2158
-    private static function _get_model_classname($model_name = null)
2159
-    {
2160
-        if (strpos($model_name, 'EE_') === 0) {
2161
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2162
-        } else {
2163
-            $model_classname = 'EEM_' . $model_name;
2164
-        }
2165
-        return $model_classname;
2166
-    }
2167
-
2168
-
2169
-    /**
2170
-     * returns the name of the primary key attribute
2171
-     *
2172
-     * @param null $classname
2173
-     * @return string
2174
-     * @throws InvalidArgumentException
2175
-     * @throws InvalidInterfaceException
2176
-     * @throws InvalidDataTypeException
2177
-     * @throws EE_Error
2178
-     * @throws ReflectionException
2179
-     */
2180
-    protected static function _get_primary_key_name($classname = null)
2181
-    {
2182
-        if (! $classname) {
2183
-            throw new EE_Error(
2184
-                sprintf(
2185
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2186
-                    $classname
2187
-                )
2188
-            );
2189
-        }
2190
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2191
-    }
2192
-
2193
-
2194
-    /**
2195
-     * Gets the value of the primary key.
2196
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2197
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2198
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2199
-     *
2200
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2201
-     * @throws InvalidArgumentException
2202
-     * @throws InvalidInterfaceException
2203
-     * @throws InvalidDataTypeException
2204
-     * @throws EE_Error
2205
-     */
2206
-    public function ID()
2207
-    {
2208
-        // now that we know the name of the variable, use a variable variable to get its value and return its
2209
-        if ($this->_model->has_primary_key_field()) {
2210
-            return $this->_fields[ $this->_model->primary_key_name() ];
2211
-        }
2212
-        return $this->_model->get_index_primary_key_string($this->_fields);
2213
-    }
2214
-
2215
-
2216
-    /**
2217
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2218
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2219
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2220
-     *
2221
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2222
-     * @param string $relationName                     eg 'Events','Question',etc.
2223
-     *                                                 an attendee to a group, you also want to specify which role they
2224
-     *                                                 will have in that group. So you would use this parameter to
2225
-     *                                                 specify array('role-column-name'=>'role-id')
2226
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2227
-     *                                                 allow you to further constrict the relation to being added.
2228
-     *                                                 However, keep in mind that the columns (keys) given must match a
2229
-     *                                                 column on the JOIN table and currently only the HABTM models
2230
-     *                                                 accept these additional conditions.  Also remember that if an
2231
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2232
-     *                                                 NEW row is created in the join table.
2233
-     * @param null   $cache_id
2234
-     * @return EE_Base_Class the object the relation was added to
2235
-     * @throws ReflectionException
2236
-     * @throws InvalidArgumentException
2237
-     * @throws InvalidInterfaceException
2238
-     * @throws InvalidDataTypeException
2239
-     * @throws EE_Error
2240
-     */
2241
-    public function _add_relation_to(
2242
-        $otherObjectModelObjectOrID,
2243
-        $relationName,
2244
-        $extra_join_model_fields_n_values = [],
2245
-        $cache_id = null
2246
-    ) {
2247
-        // if this thing exists in the DB, save the relation to the DB
2248
-        if ($this->ID()) {
2249
-            $otherObject = $this->_model->add_relationship_to(
2250
-                $this,
2251
-                $otherObjectModelObjectOrID,
2252
-                $relationName,
2253
-                $extra_join_model_fields_n_values
2254
-            );
2255
-            // clear cache so future get_many_related and get_first_related() return new results.
2256
-            $this->clear_cache($relationName, $otherObject, true);
2257
-            if ($otherObject instanceof EE_Base_Class) {
2258
-                $otherObject->clear_cache($this->_model->get_this_model_name(), $this);
2259
-            }
2260
-        } else {
2261
-            // this thing doesn't exist in the DB,  so just cache it
2262
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2263
-                throw new EE_Error(
2264
-                    sprintf(
2265
-                        esc_html__(
2266
-                            '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',
2267
-                            'event_espresso'
2268
-                        ),
2269
-                        $otherObjectModelObjectOrID,
2270
-                        get_class($this)
2271
-                    )
2272
-                );
2273
-            }
2274
-            $otherObject = $otherObjectModelObjectOrID;
2275
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2276
-        }
2277
-        if ($otherObject instanceof EE_Base_Class) {
2278
-            // fix the reciprocal relation too
2279
-            if ($otherObject->ID()) {
2280
-                // its saved so assumed relations exist in the DB, so we can just
2281
-                // clear the cache so future queries use the updated info in the DB
2282
-                $otherObject->clear_cache(
2283
-                    $this->_model->get_this_model_name(),
2284
-                    null,
2285
-                    true
2286
-                );
2287
-            } else {
2288
-                // it's not saved, so it caches relations like this
2289
-                $otherObject->cache($this->_model->get_this_model_name(), $this);
2290
-            }
2291
-        }
2292
-        return $otherObject;
2293
-    }
2294
-
2295
-
2296
-    /**
2297
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2298
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2299
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2300
-     * from the cache
2301
-     *
2302
-     * @param mixed  $otherObjectModelObjectOrID
2303
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2304
-     *                to the DB yet
2305
-     * @param string $relationName
2306
-     * @param array  $where_query
2307
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2308
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2309
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2310
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2311
-     *                deleted.
2312
-     * @return EE_Base_Class the relation was removed from
2313
-     * @throws ReflectionException
2314
-     * @throws InvalidArgumentException
2315
-     * @throws InvalidInterfaceException
2316
-     * @throws InvalidDataTypeException
2317
-     * @throws EE_Error
2318
-     */
2319
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
2320
-    {
2321
-        if ($this->ID()) {
2322
-            // if this exists in the DB, save the relation change to the DB too
2323
-            $otherObject = $this->_model->remove_relationship_to(
2324
-                $this,
2325
-                $otherObjectModelObjectOrID,
2326
-                $relationName,
2327
-                $where_query
2328
-            );
2329
-            $this->clear_cache(
2330
-                $relationName,
2331
-                $otherObject
2332
-            );
2333
-        } else {
2334
-            // this doesn't exist in the DB, just remove it from the cache
2335
-            $otherObject = $this->clear_cache(
2336
-                $relationName,
2337
-                $otherObjectModelObjectOrID
2338
-            );
2339
-        }
2340
-        if ($otherObject instanceof EE_Base_Class) {
2341
-            $otherObject->clear_cache(
2342
-                $this->_model->get_this_model_name(),
2343
-                $this
2344
-            );
2345
-        }
2346
-        return $otherObject;
2347
-    }
2348
-
2349
-
2350
-    /**
2351
-     * Removes ALL the related things for the $relationName.
2352
-     *
2353
-     * @param string $relationName
2354
-     * @param array  $where_query_params @see
2355
-     *                                   https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2356
-     * @return EE_Base_Class
2357
-     * @throws ReflectionException
2358
-     * @throws InvalidArgumentException
2359
-     * @throws InvalidInterfaceException
2360
-     * @throws InvalidDataTypeException
2361
-     * @throws EE_Error
2362
-     */
2363
-    public function _remove_relations($relationName, $where_query_params = [])
2364
-    {
2365
-        if ($this->ID()) {
2366
-            // if this exists in the DB, save the relation change to the DB too
2367
-            $otherObjects = $this->_model->remove_relations(
2368
-                $this,
2369
-                $relationName,
2370
-                $where_query_params
2371
-            );
2372
-            $this->clear_cache(
2373
-                $relationName,
2374
-                null,
2375
-                true
2376
-            );
2377
-        } else {
2378
-            // this doesn't exist in the DB, just remove it from the cache
2379
-            $otherObjects = $this->clear_cache(
2380
-                $relationName,
2381
-                null,
2382
-                true
2383
-            );
2384
-        }
2385
-        if (is_array($otherObjects)) {
2386
-            foreach ($otherObjects as $otherObject) {
2387
-                $otherObject->clear_cache(
2388
-                    $this->_model->get_this_model_name(),
2389
-                    $this
2390
-                );
2391
-            }
2392
-        }
2393
-        return $otherObjects;
2394
-    }
2395
-
2396
-
2397
-    /**
2398
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2399
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2400
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2401
-     * because we want to get even deleted items etc.
2402
-     *
2403
-     * @param string $relationName key in the model's _model_relations array
2404
-     * @param array  $query_params @see
2405
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2406
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2407
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2408
-     *                             results if you want IDs
2409
-     * @throws ReflectionException
2410
-     * @throws InvalidArgumentException
2411
-     * @throws InvalidInterfaceException
2412
-     * @throws InvalidDataTypeException
2413
-     * @throws EE_Error
2414
-     */
2415
-    public function get_many_related($relationName, $query_params = [])
2416
-    {
2417
-        if ($this->ID()) {
2418
-            // this exists in the DB, so get the related things from either the cache or the DB
2419
-            // if there are query parameters, forget about caching the related model objects.
2420
-            if ($query_params) {
2421
-                $related_model_objects = $this->_model->get_all_related(
2422
-                    $this,
2423
-                    $relationName,
2424
-                    $query_params
2425
-                );
2426
-            } else {
2427
-                // did we already cache the result of this query?
2428
-                $cached_results = $this->get_all_from_cache($relationName);
2429
-                if (! $cached_results) {
2430
-                    $related_model_objects = $this->_model->get_all_related(
2431
-                        $this,
2432
-                        $relationName,
2433
-                        $query_params
2434
-                    );
2435
-                    // if no query parameters were passed, then we got all the related model objects
2436
-                    // for that relation. We can cache them then.
2437
-                    foreach ($related_model_objects as $related_model_object) {
2438
-                        $this->cache($relationName, $related_model_object);
2439
-                    }
2440
-                } else {
2441
-                    $related_model_objects = $cached_results;
2442
-                }
2443
-            }
2444
-        } else {
2445
-            // this doesn't exist in the DB, so just get the related things from the cache
2446
-            $related_model_objects = $this->get_all_from_cache($relationName);
2447
-        }
2448
-        return $related_model_objects;
2449
-    }
2450
-
2451
-
2452
-    /**
2453
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2454
-     * unless otherwise specified in the $query_params
2455
-     *
2456
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2457
-     * @param array  $query_params   @see
2458
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2459
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2460
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2461
-     *                               that by the setting $distinct to TRUE;
2462
-     * @return int
2463
-     * @throws ReflectionException
2464
-     * @throws InvalidArgumentException
2465
-     * @throws InvalidInterfaceException
2466
-     * @throws InvalidDataTypeException
2467
-     * @throws EE_Error
2468
-     */
2469
-    public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2470
-    {
2471
-        return $this->_model->count_related(
2472
-            $this,
2473
-            $relation_name,
2474
-            $query_params,
2475
-            $field_to_count,
2476
-            $distinct
2477
-        );
2478
-    }
2479
-
2480
-
2481
-    /**
2482
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2483
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2484
-     *
2485
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2486
-     * @param array  $query_params  @see
2487
-     *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2488
-     * @param string $field_to_sum  name of field to count by.
2489
-     *                              By default, uses primary key
2490
-     *                              (which doesn't make much sense, so you should probably change it)
2491
-     * @return int
2492
-     * @throws ReflectionException
2493
-     * @throws InvalidArgumentException
2494
-     * @throws InvalidInterfaceException
2495
-     * @throws InvalidDataTypeException
2496
-     * @throws EE_Error
2497
-     */
2498
-    public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2499
-    {
2500
-        return $this->_model->sum_related(
2501
-            $this,
2502
-            $relation_name,
2503
-            $query_params,
2504
-            $field_to_sum
2505
-        );
2506
-    }
2507
-
2508
-
2509
-    /**
2510
-     * Gets the first (ie, one) related model object of the specified type.
2511
-     *
2512
-     * @param string $relationName key in the model's _model_relations array
2513
-     * @param array  $query_params @see
2514
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2515
-     * @return EE_Base_Class (not an array, a single object)
2516
-     * @throws ReflectionException
2517
-     * @throws InvalidArgumentException
2518
-     * @throws InvalidInterfaceException
2519
-     * @throws InvalidDataTypeException
2520
-     * @throws EE_Error
2521
-     */
2522
-    public function get_first_related($relationName, $query_params = [])
2523
-    {
2524
-        $model_relation = $this->_model->related_settings_for($relationName);
2525
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2526
-            // if they've provided some query parameters, don't bother trying to cache the result
2527
-            // also make sure we're not caching the result of get_first_related
2528
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
2529
-            if (
2530
-                $query_params
2531
-                || ! $model_relation instanceof EE_Belongs_To_Relation
2532
-            ) {
2533
-                $related_model_object = $this->_model->get_first_related(
2534
-                    $this,
2535
-                    $relationName,
2536
-                    $query_params
2537
-                );
2538
-            } else {
2539
-                // first, check if we've already cached the result of this query
2540
-                $cached_result = $this->get_one_from_cache($relationName);
2541
-                if (! $cached_result) {
2542
-                    $related_model_object = $this->_model->get_first_related(
2543
-                        $this,
2544
-                        $relationName,
2545
-                        $query_params
2546
-                    );
2547
-                    $this->cache($relationName, $related_model_object);
2548
-                } else {
2549
-                    $related_model_object = $cached_result;
2550
-                }
2551
-            }
2552
-        } else {
2553
-            $related_model_object = null;
2554
-            // this doesn't exist in the Db,
2555
-            // but maybe the relation is of type belongs to, and so the related thing might
2556
-            if ($model_relation instanceof EE_Belongs_To_Relation) {
2557
-                $related_model_object = $this->_model->get_first_related(
2558
-                    $this,
2559
-                    $relationName,
2560
-                    $query_params
2561
-                );
2562
-            }
2563
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2564
-            // just get what's cached on this object
2565
-            if (! $related_model_object) {
2566
-                $related_model_object = $this->get_one_from_cache($relationName);
2567
-            }
2568
-        }
2569
-        return $related_model_object;
2570
-    }
2571
-
2572
-
2573
-    /**
2574
-     * Does a delete on all related objects of type $relationName and removes
2575
-     * the current model object's relation to them. If they can't be deleted (because
2576
-     * of blocking related model objects) does nothing. If the related model objects are
2577
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2578
-     * If this model object doesn't exist yet in the DB, just removes its related things
2579
-     *
2580
-     * @param string $relationName
2581
-     * @param array  $query_params @see
2582
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2583
-     * @return int how many deleted
2584
-     * @throws ReflectionException
2585
-     * @throws InvalidArgumentException
2586
-     * @throws InvalidInterfaceException
2587
-     * @throws InvalidDataTypeException
2588
-     * @throws EE_Error
2589
-     */
2590
-    public function delete_related($relationName, $query_params = [])
2591
-    {
2592
-        if ($this->ID()) {
2593
-            $count = $this->_model->delete_related(
2594
-                $this,
2595
-                $relationName,
2596
-                $query_params
2597
-            );
2598
-        } else {
2599
-            $count = count($this->get_all_from_cache($relationName));
2600
-            $this->clear_cache($relationName, null, true);
2601
-        }
2602
-        return $count;
2603
-    }
2604
-
2605
-
2606
-    /**
2607
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2608
-     * the current model object's relation to them. If they can't be deleted (because
2609
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2610
-     * If the related thing isn't a soft-deletable model object, this function is identical
2611
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2612
-     *
2613
-     * @param string $relationName
2614
-     * @param array  $query_params @see
2615
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2616
-     * @return int how many deleted (including those soft deleted)
2617
-     * @throws ReflectionException
2618
-     * @throws InvalidArgumentException
2619
-     * @throws InvalidInterfaceException
2620
-     * @throws InvalidDataTypeException
2621
-     * @throws EE_Error
2622
-     */
2623
-    public function delete_related_permanently($relationName, $query_params = [])
2624
-    {
2625
-        if ($this->ID()) {
2626
-            $count = $this->_model->delete_related_permanently(
2627
-                $this,
2628
-                $relationName,
2629
-                $query_params
2630
-            );
2631
-        } else {
2632
-            $count = count($this->get_all_from_cache($relationName));
2633
-        }
2634
-        $this->clear_cache($relationName, null, true);
2635
-        return $count;
2636
-    }
2637
-
2638
-
2639
-    /**
2640
-     * is_set
2641
-     * Just a simple utility function children can use for checking if property exists
2642
-     *
2643
-     * @access  public
2644
-     * @param string $field_name property to check
2645
-     * @return bool                              TRUE if existing,FALSE if not.
2646
-     */
2647
-    public function is_set($field_name)
2648
-    {
2649
-        return isset($this->_fields[ $field_name ]);
2650
-    }
2651
-
2652
-
2653
-    /**
2654
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2655
-     * EE_Error exception if they don't
2656
-     *
2657
-     * @param mixed (string|array) $properties properties to check
2658
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2659
-     * @throws EE_Error
2660
-     */
2661
-    protected function _property_exists($properties)
2662
-    {
2663
-        foreach ((array)$properties as $property_name) {
2664
-            // first make sure this property exists
2665
-            if (! $this->_fields[ $property_name ]) {
2666
-                throw new EE_Error(
2667
-                    sprintf(
2668
-                        esc_html__(
2669
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2670
-                            'event_espresso'
2671
-                        ),
2672
-                        $property_name
2673
-                    )
2674
-                );
2675
-            }
2676
-        }
2677
-        return true;
2678
-    }
2679
-
2680
-
2681
-    /**
2682
-     * This simply returns an array of model fields for this object
2683
-     *
2684
-     * @return array
2685
-     * @throws InvalidArgumentException
2686
-     * @throws InvalidInterfaceException
2687
-     * @throws InvalidDataTypeException
2688
-     * @throws EE_Error
2689
-     */
2690
-    public function model_field_array()
2691
-    {
2692
-        $fields     = $this->_model->field_settings();
2693
-        $properties = [];
2694
-        // remove prepended underscore
2695
-        foreach ($fields as $field_name => $settings) {
2696
-            $properties[ $field_name ] = $this->get($field_name);
2697
-        }
2698
-        return $properties;
2699
-    }
2700
-
2701
-
2702
-    /**
2703
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2704
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2705
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2706
-     * Instead of requiring a plugin to extend the EE_Base_Class
2707
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2708
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2709
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2710
-     * and accepts 2 arguments: the object on which the function was called,
2711
-     * and an array of the original arguments passed to the function.
2712
-     * Whatever their callback function returns will be returned by this function.
2713
-     * Example: in functions.php (or in a plugin):
2714
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2715
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2716
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2717
-     *          return $previousReturnValue.$returnString;
2718
-     *      }
2719
-     * require('EE_Answer.class.php');
2720
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2721
-     * echo $answer->my_callback('monkeys',100);
2722
-     * //will output "you called my_callback! and passed args:monkeys,100"
2723
-     *
2724
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2725
-     * @param array  $args       array of original arguments passed to the function
2726
-     * @return mixed whatever the plugin which calls add_filter decides
2727
-     * @throws EE_Error
2728
-     */
2729
-    public function __call($methodName, $args)
2730
-    {
2731
-        $className = get_class($this);
2732
-        $tagName   = "FHEE__{$className}__{$methodName}";
2733
-        if (! has_filter($tagName)) {
2734
-            throw new EE_Error(
2735
-                sprintf(
2736
-                    esc_html__(
2737
-                        "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;}",
2738
-                        'event_espresso'
2739
-                    ),
2740
-                    $methodName,
2741
-                    $className,
2742
-                    $tagName
2743
-                )
2744
-            );
2745
-        }
2746
-        return apply_filters($tagName, null, $this, $args);
2747
-    }
2748
-
2749
-
2750
-    /**
2751
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2752
-     * A $previous_value can be specified in case there are many meta rows with the same key
2753
-     *
2754
-     * @param string $meta_key
2755
-     * @param mixed  $meta_value
2756
-     * @param mixed  $previous_value
2757
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2758
-     *                  NOTE: if the values haven't changed, returns 0
2759
-     * @throws InvalidArgumentException
2760
-     * @throws InvalidInterfaceException
2761
-     * @throws InvalidDataTypeException
2762
-     * @throws EE_Error
2763
-     * @throws ReflectionException
2764
-     */
2765
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2766
-    {
2767
-        $query_params = [
2768
-            [
2769
-                'EXM_key'  => $meta_key,
2770
-                'OBJ_ID'   => $this->ID(),
2771
-                'EXM_type' => $this->_model->get_this_model_name(),
2772
-            ],
2773
-        ];
2774
-        if ($previous_value !== null) {
2775
-            $query_params[0]['EXM_value'] = $meta_value;
2776
-        }
2777
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2778
-        if (! $existing_rows_like_that) {
2779
-            return $this->add_extra_meta($meta_key, $meta_value);
2780
-        }
2781
-        foreach ($existing_rows_like_that as $existing_row) {
2782
-            $existing_row->save(['EXM_value' => $meta_value]);
2783
-        }
2784
-        return count($existing_rows_like_that);
2785
-    }
2786
-
2787
-
2788
-    /**
2789
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2790
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2791
-     * extra meta row was entered, false if not
2792
-     *
2793
-     * @param string  $meta_key
2794
-     * @param mixed   $meta_value
2795
-     * @param boolean $unique
2796
-     * @return boolean
2797
-     * @throws InvalidArgumentException
2798
-     * @throws InvalidInterfaceException
2799
-     * @throws InvalidDataTypeException
2800
-     * @throws EE_Error
2801
-     * @throws ReflectionException
2802
-     * @throws ReflectionException
2803
-     */
2804
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2805
-    {
2806
-        if ($unique) {
2807
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2808
-                [
2809
-                    [
2810
-                        'EXM_key'  => $meta_key,
2811
-                        'OBJ_ID'   => $this->ID(),
2812
-                        'EXM_type' => $this->_model->get_this_model_name(),
2813
-                    ],
2814
-                ]
2815
-            );
2816
-            if ($existing_extra_meta) {
2817
-                return false;
2818
-            }
2819
-        }
2820
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2821
-            [
2822
-                'EXM_key'   => $meta_key,
2823
-                'EXM_value' => $meta_value,
2824
-                'OBJ_ID'    => $this->ID(),
2825
-                'EXM_type'  => $this->_model->get_this_model_name(),
2826
-            ]
2827
-        );
2828
-        $new_extra_meta->save();
2829
-        return true;
2830
-    }
2831
-
2832
-
2833
-    /**
2834
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2835
-     * is specified, only deletes extra meta records with that value.
2836
-     *
2837
-     * @param string $meta_key
2838
-     * @param mixed  $meta_value
2839
-     * @return int number of extra meta rows deleted
2840
-     * @throws InvalidArgumentException
2841
-     * @throws InvalidInterfaceException
2842
-     * @throws InvalidDataTypeException
2843
-     * @throws EE_Error
2844
-     * @throws ReflectionException
2845
-     */
2846
-    public function delete_extra_meta($meta_key, $meta_value = null)
2847
-    {
2848
-        $query_params = [
2849
-            [
2850
-                'EXM_key'  => $meta_key,
2851
-                'OBJ_ID'   => $this->ID(),
2852
-                'EXM_type' => $this->_model->get_this_model_name(),
2853
-            ],
2854
-        ];
2855
-        if ($meta_value !== null) {
2856
-            $query_params[0]['EXM_value'] = $meta_value;
2857
-        }
2858
-        return EEM_Extra_Meta::instance()->delete($query_params);
2859
-    }
2860
-
2861
-
2862
-    /**
2863
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2864
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2865
-     * You can specify $default is case you haven't found the extra meta
2866
-     *
2867
-     * @param string  $meta_key
2868
-     * @param boolean $single
2869
-     * @param mixed   $default if we don't find anything, what should we return?
2870
-     * @return mixed single value if $single; array if ! $single
2871
-     * @throws ReflectionException
2872
-     * @throws InvalidArgumentException
2873
-     * @throws InvalidInterfaceException
2874
-     * @throws InvalidDataTypeException
2875
-     * @throws EE_Error
2876
-     */
2877
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2878
-    {
2879
-        if ($single) {
2880
-            $result = $this->get_first_related(
2881
-                'Extra_Meta',
2882
-                [['EXM_key' => $meta_key]]
2883
-            );
2884
-            if ($result instanceof EE_Extra_Meta) {
2885
-                return $result->value();
2886
-            }
2887
-        } else {
2888
-            $results = $this->get_many_related(
2889
-                'Extra_Meta',
2890
-                [['EXM_key' => $meta_key]]
2891
-            );
2892
-            if ($results) {
2893
-                $values = [];
2894
-                foreach ($results as $result) {
2895
-                    if ($result instanceof EE_Extra_Meta) {
2896
-                        $values[ $result->ID() ] = $result->value();
2897
-                    }
2898
-                }
2899
-                return $values;
2900
-            }
2901
-        }
2902
-        // if nothing discovered yet return default.
2903
-        return apply_filters(
2904
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2905
-            $default,
2906
-            $meta_key,
2907
-            $single,
2908
-            $this
2909
-        );
2910
-    }
2911
-
2912
-
2913
-    /**
2914
-     * Returns a simple array of all the extra meta associated with this model object.
2915
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2916
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2917
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2918
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2919
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2920
-     * finally the extra meta's value as each sub-value. (eg
2921
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2922
-     *
2923
-     * @param boolean $one_of_each_key
2924
-     * @return array
2925
-     * @throws ReflectionException
2926
-     * @throws InvalidArgumentException
2927
-     * @throws InvalidInterfaceException
2928
-     * @throws InvalidDataTypeException
2929
-     * @throws EE_Error
2930
-     */
2931
-    public function all_extra_meta_array($one_of_each_key = true)
2932
-    {
2933
-        $return_array = [];
2934
-        if ($one_of_each_key) {
2935
-            $extra_meta_objs = $this->get_many_related(
2936
-                'Extra_Meta',
2937
-                ['group_by' => 'EXM_key']
2938
-            );
2939
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2940
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2941
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2942
-                }
2943
-            }
2944
-        } else {
2945
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2946
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2947
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2948
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2949
-                        $return_array[ $extra_meta_obj->key() ] = [];
2950
-                    }
2951
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2952
-                }
2953
-            }
2954
-        }
2955
-        return $return_array;
2956
-    }
2957
-
2958
-
2959
-    /**
2960
-     * Gets a pretty nice displayable nice for this model object. Often overridden
2961
-     *
2962
-     * @return string
2963
-     * @throws InvalidArgumentException
2964
-     * @throws InvalidInterfaceException
2965
-     * @throws InvalidDataTypeException
2966
-     * @throws EE_Error
2967
-     */
2968
-    public function name()
2969
-    {
2970
-        // find a field that's not a text field
2971
-        $field_we_can_use = $this->_model->get_a_field_of_type('EE_Text_Field_Base');
2972
-        if ($field_we_can_use) {
2973
-            return $this->get($field_we_can_use->get_name());
2974
-        }
2975
-        $first_few_properties = $this->model_field_array();
2976
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
2977
-        $name_parts           = [];
2978
-        foreach ($first_few_properties as $name => $value) {
2979
-            $name_parts[] = "$name:$value";
2980
-        }
2981
-        return implode(',', $name_parts);
2982
-    }
2983
-
2984
-
2985
-    /**
2986
-     * in_entity_map
2987
-     * Checks if this model object has been proven to already be in the entity map
2988
-     *
2989
-     * @return boolean
2990
-     * @throws InvalidArgumentException
2991
-     * @throws InvalidInterfaceException
2992
-     * @throws InvalidDataTypeException
2993
-     * @throws EE_Error
2994
-     */
2995
-    public function in_entity_map()
2996
-    {
2997
-        // well, if we looked, did we find it in the entity map?
2998
-        return $this->ID() && $this->_model->get_from_entity_map($this->ID()) === $this;
2999
-    }
3000
-
3001
-
3002
-    /**
3003
-     * refresh_from_db
3004
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3005
-     *
3006
-     * @throws ReflectionException
3007
-     * @throws InvalidArgumentException
3008
-     * @throws InvalidInterfaceException
3009
-     * @throws InvalidDataTypeException
3010
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3011
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3012
-     */
3013
-    public function refresh_from_db()
3014
-    {
3015
-        if ($this->ID() && $this->in_entity_map()) {
3016
-            $this->_model->refresh_entity_map_from_db($this->ID());
3017
-        } else {
3018
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3019
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
3020
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3021
-            // absolutely nothing in it for this ID
3022
-            if (WP_DEBUG) {
3023
-                throw new EE_Error(
3024
-                    sprintf(
3025
-                        esc_html__(
3026
-                            '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.',
3027
-                            'event_espresso'
3028
-                        ),
3029
-                        $this->ID(),
3030
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3031
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3032
-                    )
3033
-                );
3034
-            }
3035
-        }
3036
-    }
3037
-
3038
-
3039
-    /**
3040
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3041
-     *
3042
-     * @param EE_Model_Field_Base[] $fields
3043
-     * @param string                $new_value_sql
3044
-     *          example: 'column_name=123',
3045
-     *          or 'column_name=column_name+1',
3046
-     *          or 'column_name= CASE
3047
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3048
-     *          THEN `column_name` + 5
3049
-     *          ELSE `column_name`
3050
-     *          END'
3051
-     *          Also updates $field on this model object with the latest value from the database.
3052
-     * @return bool
3053
-     * @throws EE_Error
3054
-     * @throws InvalidArgumentException
3055
-     * @throws InvalidDataTypeException
3056
-     * @throws InvalidInterfaceException
3057
-     * @throws ReflectionException
3058
-     * @since 4.9.80.p
3059
-     */
3060
-    protected function updateFieldsInDB($fields, $new_value_sql)
3061
-    {
3062
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3063
-        // if it wasn't even there to start off.
3064
-        if (! $this->ID()) {
3065
-            $this->save();
3066
-        }
3067
-        global $wpdb;
3068
-        if (empty($fields)) {
3069
-            throw new InvalidArgumentException(
3070
-                esc_html__(
3071
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3072
-                    'event_espresso'
3073
-                )
3074
-            );
3075
-        }
3076
-        $first_field = reset($fields);
3077
-        $table_alias = $first_field->get_table_alias();
3078
-        foreach ($fields as $field) {
3079
-            if ($table_alias !== $field->get_table_alias()) {
3080
-                throw new InvalidArgumentException(
3081
-                    sprintf(
3082
-                        esc_html__(
3083
-                        // @codingStandardsIgnoreStart
3084
-                            '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.',
3085
-                            // @codingStandardsIgnoreEnd
3086
-                            'event_espresso'
3087
-                        ),
3088
-                        $table_alias,
3089
-                        $field->get_table_alias()
3090
-                    )
3091
-                );
3092
-            }
3093
-        }
3094
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3095
-        $table_obj      = $this->_model->get_table_obj_by_alias($table_alias);
3096
-        $table_pk_value = $this->ID();
3097
-        $table_name     = $table_obj->get_table_name();
3098
-        if ($table_obj instanceof EE_Secondary_Table) {
3099
-            $table_pk_field_name = $table_obj->get_fk_on_table();
3100
-        } else {
3101
-            $table_pk_field_name = $table_obj->get_pk_column();
3102
-        }
3103
-
3104
-        $query  =
3105
-            "UPDATE `{$table_name}`
16
+	/**
17
+	 * This is an array of the original properties and values provided during construction
18
+	 * of this model object. (keys are model field names, values are their values).
19
+	 * This list is important to remember so that when we are merging data from the db, we know
20
+	 * which values to override and which to not override.
21
+	 *
22
+	 * @var array
23
+	 */
24
+	protected $_props_n_values_provided_in_constructor;
25
+
26
+	/**
27
+	 * Timezone
28
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
+	 * access to it.
32
+	 *
33
+	 * @var string
34
+	 */
35
+	protected $_timezone;
36
+
37
+	/**
38
+	 * date format
39
+	 * pattern or format for displaying dates
40
+	 *
41
+	 * @var string $_dt_frmt
42
+	 */
43
+	protected $_dt_frmt;
44
+
45
+	/**
46
+	 * time format
47
+	 * pattern or format for displaying time
48
+	 *
49
+	 * @var string $_tm_frmt
50
+	 */
51
+	protected $_tm_frmt;
52
+
53
+	/**
54
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
55
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
56
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
+	 *
59
+	 * @var array
60
+	 */
61
+	protected $_cached_properties = [];
62
+
63
+	/**
64
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
65
+	 * single
66
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
+	 * all others have an array)
69
+	 *
70
+	 * @var array
71
+	 */
72
+	protected $_model_relations = [];
73
+
74
+	/**
75
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
+	 *
78
+	 * @var array
79
+	 */
80
+	protected $_fields = [];
81
+
82
+	/**
83
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
84
+	 * For example, we might create model objects intended to only be used for the duration
85
+	 * of this request and to be thrown away, and if they were accidentally saved
86
+	 * it would be a bug.
87
+	 */
88
+	protected $_allow_persist = true;
89
+
90
+	/**
91
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
92
+	 */
93
+	protected $_has_changes = false;
94
+
95
+	/**
96
+	 * @var EEM_Base
97
+	 */
98
+	protected $_model;
99
+
100
+	/**
101
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
+	 * array as:
107
+	 * array(
108
+	 *  'Registration_Count' => 24
109
+	 * );
110
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
+	 * info)
113
+	 *
114
+	 * @var array
115
+	 */
116
+	protected $custom_selection_results = [];
117
+
118
+
119
+	/**
120
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
+	 * play nice
122
+	 *
123
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
126
+	 * @param boolean $by_db                                   a flag for setting if the class is instantiated by the
127
+	 *                                                         corresponding db model or not.
128
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
+	 *                                                         be in when instantiating a EE_Base_Class object.
130
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
131
+	 *                                                         value is the date_format and second value is the time
132
+	 *                                                         format.
133
+	 * @throws InvalidArgumentException
134
+	 * @throws InvalidInterfaceException
135
+	 * @throws InvalidDataTypeException
136
+	 * @throws EE_Error
137
+	 * @throws ReflectionException
138
+	 */
139
+	protected function __construct($fieldValues = [], $by_db = false, $timezone = '', $date_formats = [])
140
+	{
141
+		$className = get_class($this);
142
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
+		$this->_model = $this->get_model();
144
+		$model_fields = $this->_model->field_settings();
145
+		// ensure $fieldValues is an array
146
+		$fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
147
+		// verify client code has not passed any invalid field names
148
+		foreach ($fieldValues as $field_name => $field_value) {
149
+			if (! isset($model_fields[ $field_name ])) {
150
+				throw new EE_Error(
151
+					sprintf(
152
+						esc_html__(
153
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
+							'event_espresso'
155
+						),
156
+						$field_name,
157
+						get_class($this),
158
+						implode(', ', array_keys($model_fields))
159
+					)
160
+				);
161
+			}
162
+		}
163
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
+		if (! empty($date_formats) && is_array($date_formats)) {
165
+			list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
166
+		} else {
167
+			// set default formats for date and time
168
+			$this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
169
+			$this->_tm_frmt = (string)get_option('time_format', 'g:i a');
170
+		}
171
+		// if db model is instantiating
172
+		if ($by_db) {
173
+			// client code has indicated these field values are from the database
174
+			foreach ($model_fields as $fieldName => $field) {
175
+				$this->set_from_db(
176
+					$fieldName,
177
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
+				);
179
+			}
180
+		} else {
181
+			// we're constructing a brand
182
+			// new instance of the model object. Generally, this means we'll need to do more field validation
183
+			foreach ($model_fields as $fieldName => $field) {
184
+				$this->set(
185
+					$fieldName,
186
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
+					true
188
+				);
189
+			}
190
+		}
191
+		// remember what values were passed to this constructor
192
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
193
+		// remember in entity mapper
194
+		if (! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
195
+			$this->_model->add_to_entity_map($this);
196
+		}
197
+		// setup all the relations
198
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
199
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
+				$this->_model_relations[ $relation_name ] = null;
201
+			} else {
202
+				$this->_model_relations[ $relation_name ] = [];
203
+			}
204
+		}
205
+		/**
206
+		 * Action done at the end of each model object construction
207
+		 *
208
+		 * @param EE_Base_Class $this the model object just created
209
+		 */
210
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
+	}
212
+
213
+
214
+	/**
215
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
216
+	 *
217
+	 * @return boolean
218
+	 */
219
+	public function allow_persist()
220
+	{
221
+		return $this->_allow_persist;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
227
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
+	 * you got new information that somehow made you change your mind.
229
+	 *
230
+	 * @param boolean $allow_persist
231
+	 * @return boolean
232
+	 */
233
+	public function set_allow_persist($allow_persist)
234
+	{
235
+		return $this->_allow_persist = $allow_persist;
236
+	}
237
+
238
+
239
+	/**
240
+	 * Gets the field's original value when this object was constructed during this request.
241
+	 * This can be helpful when determining if a model object has changed or not
242
+	 *
243
+	 * @param string $field_name
244
+	 * @return mixed|null
245
+	 * @throws InvalidArgumentException
246
+	 * @throws InvalidInterfaceException
247
+	 * @throws InvalidDataTypeException
248
+	 * @throws EE_Error
249
+	 */
250
+	public function get_original($field_name)
251
+	{
252
+		if (
253
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
254
+			&& $field_settings = $this->_model->field_settings_for($field_name)
255
+		) {
256
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
257
+		}
258
+		return null;
259
+	}
260
+
261
+
262
+	/**
263
+	 * @param EE_Base_Class $obj
264
+	 * @return string
265
+	 */
266
+	public function get_class($obj)
267
+	{
268
+		return get_class($obj);
269
+	}
270
+
271
+
272
+	/**
273
+	 * Overrides parent because parent expects old models.
274
+	 * This also doesn't do any validation, and won't work for serialized arrays
275
+	 *
276
+	 * @param string $field_name
277
+	 * @param mixed  $field_value
278
+	 * @param bool   $use_default
279
+	 * @throws InvalidArgumentException
280
+	 * @throws InvalidInterfaceException
281
+	 * @throws InvalidDataTypeException
282
+	 * @throws EE_Error
283
+	 * @throws ReflectionException
284
+	 * @throws ReflectionException
285
+	 * @throws ReflectionException
286
+	 */
287
+	public function set($field_name, $field_value, $use_default = false)
288
+	{
289
+		// if not using default and nothing has changed, and object has already been setup (has ID),
290
+		// then don't do anything
291
+		if (
292
+			! $use_default
293
+			&& $this->_fields[ $field_name ] === $field_value
294
+			&& $this->ID()
295
+		) {
296
+			return;
297
+		}
298
+		$this->_has_changes = true;
299
+		$field_obj          = $this->_model->field_settings_for($field_name);
300
+		if ($field_obj instanceof EE_Model_Field_Base) {
301
+			// if ( method_exists( $field_obj, 'set_timezone' )) {
302
+			if ($field_obj instanceof EE_Datetime_Field) {
303
+				$field_obj->set_timezone($this->_timezone);
304
+				$field_obj->set_date_format($this->_dt_frmt);
305
+				$field_obj->set_time_format($this->_tm_frmt);
306
+			}
307
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
308
+			// should the value be null?
309
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
310
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
311
+				/**
312
+				 * To save having to refactor all the models, if a default value is used for a
313
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
314
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
315
+				 * object.
316
+				 *
317
+				 * @since 4.6.10+
318
+				 */
319
+				if (
320
+					$field_obj instanceof EE_Datetime_Field
321
+					&& $this->_fields[ $field_name ] !== null
322
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
323
+				) {
324
+					empty($this->_fields[ $field_name ])
325
+						? $this->set($field_name, time())
326
+						: $this->set($field_name, $this->_fields[ $field_name ]);
327
+				}
328
+			} else {
329
+				$this->_fields[ $field_name ] = $holder_of_value;
330
+			}
331
+			// if we're not in the constructor...
332
+			// now check if what we set was a primary key
333
+			if (
334
+				// note: props_n_values_provided_in_constructor is only set at the END of the constructor
335
+				$this->_props_n_values_provided_in_constructor
336
+				&& $field_value
337
+				&& $field_name === $this->_model->primary_key_name()
338
+			) {
339
+				// if so, we want all this object's fields to be filled either with
340
+				// what we've explicitly set on this model
341
+				// or what we have in the db
342
+				// echo "setting primary key!";
343
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
344
+				$obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
345
+				foreach ($fields_on_model as $field_obj) {
346
+					if (
347
+						! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
348
+						&& $field_obj->get_name() !== $field_name
349
+					) {
350
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
351
+					}
352
+				}
353
+				// oh this model object has an ID? well make sure its in the entity mapper
354
+				$this->_model->add_to_entity_map($this);
355
+			}
356
+			// let's unset any cache for this field_name from the $_cached_properties property.
357
+			$this->_clear_cached_property($field_name);
358
+		} else {
359
+			throw new EE_Error(
360
+				sprintf(
361
+					esc_html__(
362
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
363
+						'event_espresso'
364
+					),
365
+					$field_name
366
+				)
367
+			);
368
+		}
369
+	}
370
+
371
+
372
+	/**
373
+	 * Set custom select values for model.
374
+	 *
375
+	 * @param array $custom_select_values
376
+	 */
377
+	public function setCustomSelectsValues(array $custom_select_values)
378
+	{
379
+		$this->custom_selection_results = $custom_select_values;
380
+	}
381
+
382
+
383
+	/**
384
+	 * Returns the custom select value for the provided alias if its set.
385
+	 * If not set, returns null.
386
+	 *
387
+	 * @param string $alias
388
+	 * @return string|int|float|null
389
+	 */
390
+	public function getCustomSelect($alias)
391
+	{
392
+		return isset($this->custom_selection_results[ $alias ])
393
+			? $this->custom_selection_results[ $alias ]
394
+			: null;
395
+	}
396
+
397
+
398
+	/**
399
+	 * This sets the field value on the db column if it exists for the given $column_name or
400
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
401
+	 *
402
+	 * @param string $field_name  Must be the exact column name.
403
+	 * @param mixed  $field_value The value to set.
404
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
405
+	 * @throws InvalidArgumentException
406
+	 * @throws InvalidInterfaceException
407
+	 * @throws InvalidDataTypeException
408
+	 * @throws EE_Error
409
+	 * @throws ReflectionException
410
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
411
+	 */
412
+	public function set_field_or_extra_meta($field_name, $field_value)
413
+	{
414
+		if ($this->_model->has_field($field_name)) {
415
+			$this->set($field_name, $field_value);
416
+			return true;
417
+		}
418
+		// ensure this object is saved first so that extra meta can be properly related.
419
+		$this->save();
420
+		return $this->update_extra_meta($field_name, $field_value);
421
+	}
422
+
423
+
424
+	/**
425
+	 * This retrieves the value of the db column set on this class or if that's not present
426
+	 * it will attempt to retrieve from extra_meta if found.
427
+	 * Example Usage:
428
+	 * Via EE_Message child class:
429
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
430
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
431
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
432
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
433
+	 * value for those extra fields dynamically via the EE_message object.
434
+	 *
435
+	 * @param string $field_name expecting the fully qualified field name.
436
+	 * @return mixed|null  value for the field if found.  null if not found.
437
+	 * @throws ReflectionException
438
+	 * @throws InvalidArgumentException
439
+	 * @throws InvalidInterfaceException
440
+	 * @throws InvalidDataTypeException
441
+	 * @throws EE_Error
442
+	 */
443
+	public function get_field_or_extra_meta($field_name)
444
+	{
445
+		if ($this->_model->has_field($field_name)) {
446
+			$column_value = $this->get($field_name);
447
+		} else {
448
+			// This isn't a column in the main table, let's see if it is in the extra meta.
449
+			$column_value = $this->get_extra_meta($field_name, true);
450
+		}
451
+		return $column_value;
452
+	}
453
+
454
+
455
+	/**
456
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
457
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
458
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
459
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
460
+	 *
461
+	 * @access public
462
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
463
+	 * @return void
464
+	 * @throws InvalidArgumentException
465
+	 * @throws InvalidInterfaceException
466
+	 * @throws InvalidDataTypeException
467
+	 */
468
+	public function set_timezone($timezone = '')
469
+	{
470
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
471
+		// make sure we clear all cached properties because they won't be relevant now
472
+		$this->_clear_cached_properties();
473
+		// make sure we update field settings and the date for all EE_Datetime_Fields
474
+		$model_fields = $this->_model->field_settings();
475
+		foreach ($model_fields as $field_name => $field_obj) {
476
+			if ($field_obj instanceof EE_Datetime_Field) {
477
+				$field_obj->set_timezone($this->_timezone);
478
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
479
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
480
+				}
481
+			}
482
+		}
483
+	}
484
+
485
+
486
+	/**
487
+	 * This just returns whatever is set for the current timezone.
488
+	 *
489
+	 * @access public
490
+	 * @return string timezone string
491
+	 */
492
+	public function get_timezone()
493
+	{
494
+		return $this->_timezone;
495
+	}
496
+
497
+
498
+	/**
499
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
500
+	 * internally instead of wp set date format options
501
+	 *
502
+	 * @param string $format should be a format recognizable by PHP date() functions.
503
+	 * @since 4.6
504
+	 */
505
+	public function set_date_format($format)
506
+	{
507
+		$this->_dt_frmt = $format;
508
+		// clear cached_properties because they won't be relevant now.
509
+		$this->_clear_cached_properties();
510
+	}
511
+
512
+
513
+	/**
514
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
515
+	 * class internally instead of wp set time format options.
516
+	 *
517
+	 * @param string $format should be a format recognizable by PHP date() functions.
518
+	 * @since 4.6
519
+	 */
520
+	public function set_time_format($format)
521
+	{
522
+		$this->_tm_frmt = $format;
523
+		// clear cached_properties because they won't be relevant now.
524
+		$this->_clear_cached_properties();
525
+	}
526
+
527
+
528
+	/**
529
+	 * This returns the current internal set format for the date and time formats.
530
+	 *
531
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
532
+	 *                             where the first value is the date format and the second value is the time format.
533
+	 * @return mixed string|array
534
+	 */
535
+	public function get_format($full = true)
536
+	{
537
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
538
+	}
539
+
540
+
541
+	/**
542
+	 * cache
543
+	 * stores the passed model object on the current model object.
544
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
545
+	 *
546
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
547
+	 *                                       'Registration' associated with this model object
548
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
549
+	 *                                       that could be a payment or a registration)
550
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
551
+	 *                                       items which will be stored in an array on this object
552
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
553
+	 *                                       related thing, no array)
554
+	 * @throws InvalidArgumentException
555
+	 * @throws InvalidInterfaceException
556
+	 * @throws InvalidDataTypeException
557
+	 * @throws EE_Error
558
+	 */
559
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
560
+	{
561
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
562
+		if (! $object_to_cache instanceof EE_Base_Class) {
563
+			return false;
564
+		}
565
+		// also get "how" the object is related, or throw an error
566
+		if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
567
+			throw new EE_Error(
568
+				sprintf(
569
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
570
+					$relationName,
571
+					get_class($this)
572
+				)
573
+			);
574
+		}
575
+		// how many things are related ?
576
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
577
+			// if it's a "belongs to" relationship, then there's only one related model object
578
+			// eg, if this is a registration, there's only 1 attendee for it
579
+			// so for these model objects just set it to be cached
580
+			$this->_model_relations[ $relationName ] = $object_to_cache;
581
+			$return                                  = true;
582
+		} else {
583
+			// otherwise, this is the "many" side of a one to many relationship,
584
+			// so we'll add the object to the array of related objects for that type.
585
+			// eg: if this is an event, there are many registrations for that event,
586
+			// so we cache the registrations in an array
587
+			if (! is_array($this->_model_relations[ $relationName ])) {
588
+				// if for some reason, the cached item is a model object,
589
+				// then stick that in the array, otherwise start with an empty array
590
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
591
+														   instanceof
592
+														   EE_Base_Class
593
+					? [$this->_model_relations[ $relationName ]] : [];
594
+			}
595
+			// first check for a cache_id which is normally empty
596
+			if (! empty($cache_id)) {
597
+				// if the cache_id exists, then it means we are purposely trying to cache this
598
+				// with a known key that can then be used to retrieve the object later on
599
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
600
+				$return                                               = $cache_id;
601
+			} elseif ($object_to_cache->ID()) {
602
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
603
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
604
+				$return                                                            = $object_to_cache->ID();
605
+			} else {
606
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
607
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
608
+				// move the internal pointer to the end of the array
609
+				end($this->_model_relations[ $relationName ]);
610
+				// and grab the key so that we can return it
611
+				$return = key($this->_model_relations[ $relationName ]);
612
+			}
613
+		}
614
+		return $return;
615
+	}
616
+
617
+
618
+	/**
619
+	 * For adding an item to the cached_properties property.
620
+	 *
621
+	 * @access protected
622
+	 * @param string      $field_name the property item the corresponding value is for.
623
+	 * @param mixed       $value      The value we are caching.
624
+	 * @param string|null $cache_type
625
+	 * @return void
626
+	 * @throws InvalidArgumentException
627
+	 * @throws InvalidInterfaceException
628
+	 * @throws InvalidDataTypeException
629
+	 * @throws EE_Error
630
+	 */
631
+	protected function _set_cached_property($field_name, $value, $cache_type = null)
632
+	{
633
+		// first make sure this property exists
634
+		$this->_model->field_settings_for($field_name);
635
+		$cache_type                                             = empty($cache_type) ? 'standard' : $cache_type;
636
+		$this->_cached_properties[ $field_name ][ $cache_type ] = $value;
637
+	}
638
+
639
+
640
+	/**
641
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
642
+	 * This also SETS the cache if we return the actual property!
643
+	 *
644
+	 * @param string $field_name       the name of the property we're trying to retrieve
645
+	 * @param bool   $pretty
646
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
647
+	 *                                 (in cases where the same property may be used for different outputs
648
+	 *                                 - i.e. datetime, money etc.)
649
+	 *                                 It can also accept certain pre-defined "schema" strings
650
+	 *                                 to define how to output the property.
651
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
652
+	 * @return mixed                   whatever the value for the property is we're retrieving
653
+	 * @throws InvalidArgumentException
654
+	 * @throws InvalidInterfaceException
655
+	 * @throws InvalidDataTypeException
656
+	 * @throws EE_Error
657
+	 */
658
+	protected function _get_cached_property($field_name, $pretty = false, $extra_cache_ref = null)
659
+	{
660
+		// verify the field exists
661
+		$this->_model->field_settings_for($field_name);
662
+		$cache_type = $pretty ? 'pretty' : 'standard';
663
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
664
+		if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
665
+			return $this->_cached_properties[ $field_name ][ $cache_type ];
666
+		}
667
+		$value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
668
+		$this->_set_cached_property($field_name, $value, $cache_type);
669
+		return $value;
670
+	}
671
+
672
+
673
+	/**
674
+	 * If the cache didn't fetch the needed item, this fetches it.
675
+	 *
676
+	 * @param string $field_name
677
+	 * @param bool   $pretty
678
+	 * @param string $extra_cache_ref
679
+	 * @return mixed
680
+	 * @throws InvalidArgumentException
681
+	 * @throws InvalidInterfaceException
682
+	 * @throws InvalidDataTypeException
683
+	 * @throws EE_Error
684
+	 */
685
+	protected function _get_fresh_property($field_name, $pretty = false, $extra_cache_ref = null)
686
+	{
687
+		$field_obj = $this->_model->field_settings_for($field_name);
688
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
689
+		if ($field_obj instanceof EE_Datetime_Field) {
690
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
691
+		}
692
+		if (! isset($this->_fields[ $field_name ])) {
693
+			$this->_fields[ $field_name ] = null;
694
+		}
695
+		return $pretty
696
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
697
+			: $field_obj->prepare_for_get($this->_fields[ $field_name ]);
698
+	}
699
+
700
+
701
+	/**
702
+	 * set timezone, formats, and output for EE_Datetime_Field objects
703
+	 *
704
+	 * @param EE_Datetime_Field $datetime_field
705
+	 * @param bool              $pretty
706
+	 * @param null              $date_or_time
707
+	 * @return void
708
+	 * @throws InvalidArgumentException
709
+	 * @throws InvalidInterfaceException
710
+	 * @throws InvalidDataTypeException
711
+	 */
712
+	protected function _prepare_datetime_field(
713
+		EE_Datetime_Field $datetime_field,
714
+		$pretty = false,
715
+		$date_or_time = null
716
+	) {
717
+		$datetime_field->set_timezone($this->_timezone);
718
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
719
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
720
+		// set the output returned
721
+		switch ($date_or_time) {
722
+			case 'D':
723
+				$datetime_field->set_date_time_output('date');
724
+				break;
725
+			case 'T':
726
+				$datetime_field->set_date_time_output('time');
727
+				break;
728
+			default:
729
+				$datetime_field->set_date_time_output();
730
+		}
731
+	}
732
+
733
+
734
+	/**
735
+	 * This just takes care of clearing out the cached_properties
736
+	 *
737
+	 * @return void
738
+	 */
739
+	protected function _clear_cached_properties()
740
+	{
741
+		$this->_cached_properties = [];
742
+	}
743
+
744
+
745
+	/**
746
+	 * This just clears out ONE property if it exists in the cache
747
+	 *
748
+	 * @param string $property_name the property to remove if it exists (from the _cached_properties array)
749
+	 * @return void
750
+	 */
751
+	protected function _clear_cached_property($property_name)
752
+	{
753
+		if (isset($this->_cached_properties[ $property_name ])) {
754
+			unset($this->_cached_properties[ $property_name ]);
755
+		}
756
+	}
757
+
758
+
759
+	/**
760
+	 * Ensures that this related thing is a model object.
761
+	 *
762
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
763
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
764
+	 * @return EE_Base_Class
765
+	 * @throws ReflectionException
766
+	 * @throws InvalidArgumentException
767
+	 * @throws InvalidInterfaceException
768
+	 * @throws InvalidDataTypeException
769
+	 * @throws EE_Error
770
+	 */
771
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
772
+	{
773
+		$other_model_instance = self::_get_model_instance_with_name(
774
+			self::_get_model_classname($model_name),
775
+			$this->_timezone
776
+		);
777
+		return $other_model_instance->ensure_is_obj($object_or_id);
778
+	}
779
+
780
+
781
+	/**
782
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
783
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
784
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
785
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
786
+	 *
787
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
788
+	 *                                                     Eg 'Registration'
789
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
790
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
791
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
792
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
793
+	 *                                                     this is HasMany or HABTM.
794
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
795
+	 *                                                     relation from all
796
+	 * @throws InvalidArgumentException
797
+	 * @throws InvalidInterfaceException
798
+	 * @throws InvalidDataTypeException
799
+	 * @throws EE_Error
800
+	 * @throws ReflectionException
801
+	 */
802
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
803
+	{
804
+		$relationship_to_model = $this->_model->related_settings_for($relationName);
805
+		$index_in_cache        = '';
806
+		if (! $relationship_to_model) {
807
+			throw new EE_Error(
808
+				sprintf(
809
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
810
+					$relationName,
811
+					get_class($this)
812
+				)
813
+			);
814
+		}
815
+		if ($clear_all) {
816
+			$obj_removed                             = true;
817
+			$this->_model_relations[ $relationName ] = null;
818
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
819
+			$obj_removed                             = $this->_model_relations[ $relationName ];
820
+			$this->_model_relations[ $relationName ] = null;
821
+		} else {
822
+			if (
823
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
824
+				&& $object_to_remove_or_index_into_array->ID()
825
+			) {
826
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
827
+				if (
828
+					is_array($this->_model_relations[ $relationName ])
829
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
830
+				) {
831
+					$index_found_at = null;
832
+					// find this object in the array even though it has a different key
833
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
834
+						/** @noinspection TypeUnsafeComparisonInspection */
835
+						if (
836
+							$obj instanceof EE_Base_Class
837
+							&& (
838
+								$obj == $object_to_remove_or_index_into_array
839
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
840
+							)
841
+						) {
842
+							$index_found_at = $index;
843
+							break;
844
+						}
845
+					}
846
+					if ($index_found_at) {
847
+						$index_in_cache = $index_found_at;
848
+					} else {
849
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
850
+						// if it wasn't in it to begin with. So we're done
851
+						return $object_to_remove_or_index_into_array;
852
+					}
853
+				}
854
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
855
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
856
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
857
+					/** @noinspection TypeUnsafeComparisonInspection */
858
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
859
+						$index_in_cache = $index;
860
+					}
861
+				}
862
+			} else {
863
+				$index_in_cache = $object_to_remove_or_index_into_array;
864
+			}
865
+			// supposedly we've found it. But it could just be that the client code
866
+			// provided a bad index/object
867
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
868
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
869
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
870
+			} else {
871
+				// that thing was never cached anyways.
872
+				$obj_removed = null;
873
+			}
874
+		}
875
+		return $obj_removed;
876
+	}
877
+
878
+
879
+	/**
880
+	 * update_cache_after_object_save
881
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
882
+	 * obtained after being saved to the db
883
+	 *
884
+	 * @param string        $relationName       - the type of object that is cached
885
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
886
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
887
+	 * @return boolean TRUE on success, FALSE on fail
888
+	 * @throws InvalidArgumentException
889
+	 * @throws InvalidInterfaceException
890
+	 * @throws InvalidDataTypeException
891
+	 * @throws EE_Error
892
+	 */
893
+	public function update_cache_after_object_save(
894
+		$relationName,
895
+		EE_Base_Class $newly_saved_object,
896
+		$current_cache_id = ''
897
+	) {
898
+		// verify that incoming object is of the correct type
899
+		$obj_class = 'EE_' . $relationName;
900
+		if ($newly_saved_object instanceof $obj_class) {
901
+			/* @type EE_Base_Class $newly_saved_object */
902
+			// now get the type of relation
903
+			$relationship_to_model = $this->_model->related_settings_for($relationName);
904
+			// if this is a 1:1 relationship
905
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
906
+				// then just replace the cached object with the newly saved object
907
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
908
+				return true;
909
+				// or if it's some kind of sordid feral polyamorous relationship...
910
+			}
911
+			if (
912
+				is_array($this->_model_relations[ $relationName ])
913
+				&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
914
+			) {
915
+				// then remove the current cached item
916
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
917
+				// and cache the newly saved object using it's new ID
918
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
919
+				return true;
920
+			}
921
+		}
922
+		return false;
923
+	}
924
+
925
+
926
+	/**
927
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
928
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
929
+	 *
930
+	 * @param string $relationName
931
+	 * @return EE_Base_Class
932
+	 */
933
+	public function get_one_from_cache($relationName)
934
+	{
935
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
936
+			? $this->_model_relations[ $relationName ]
937
+			: null;
938
+		if (is_array($cached_array_or_object)) {
939
+			return array_shift($cached_array_or_object);
940
+		}
941
+		return $cached_array_or_object;
942
+	}
943
+
944
+
945
+	/**
946
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
947
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
948
+	 *
949
+	 * @param string $relationName
950
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
951
+	 * @throws InvalidArgumentException
952
+	 * @throws InvalidInterfaceException
953
+	 * @throws InvalidDataTypeException
954
+	 * @throws EE_Error
955
+	 * @throws ReflectionException
956
+	 */
957
+	public function get_all_from_cache($relationName)
958
+	{
959
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
960
+		// if the result is not an array, but exists, make it an array
961
+		$objects = is_array($objects) ? $objects : [$objects];
962
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
963
+		// basically, if this model object was stored in the session, and these cached model objects
964
+		// already have IDs, let's make sure they're in their model's entity mapper
965
+		// otherwise we will have duplicates next time we call
966
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
967
+		$related_model = EE_Registry::instance()->load_model($relationName);
968
+		foreach ($objects as $model_object) {
969
+			if ($related_model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
970
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
971
+				if ($model_object->ID()) {
972
+					$related_model->add_to_entity_map($model_object);
973
+				}
974
+			} else {
975
+				throw new EE_Error(
976
+					sprintf(
977
+						esc_html__(
978
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
979
+							'event_espresso'
980
+						),
981
+						$relationName,
982
+						gettype($model_object)
983
+					)
984
+				);
985
+			}
986
+		}
987
+		return $objects;
988
+	}
989
+
990
+
991
+	/**
992
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
993
+	 * matching the given query conditions.
994
+	 *
995
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
996
+	 * @param int   $limit              How many objects to return.
997
+	 * @param array $query_params       Any additional conditions on the query.
998
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
999
+	 *                                  you can indicate just the columns you want returned
1000
+	 * @return array|EE_Base_Class[]
1001
+	 * @throws ReflectionException
1002
+	 * @throws InvalidArgumentException
1003
+	 * @throws InvalidInterfaceException
1004
+	 * @throws InvalidDataTypeException
1005
+	 * @throws EE_Error
1006
+	 */
1007
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1008
+	{
1009
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1010
+			? $this->_model->get_primary_key_field()->get_name()
1011
+			: $field_to_order_by;
1012
+		$current_value = ! empty($field) ? $this->get($field) : null;
1013
+		if (empty($field) || empty($current_value)) {
1014
+			return [];
1015
+		}
1016
+		return $this->_model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1017
+	}
1018
+
1019
+
1020
+	/**
1021
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1022
+	 * matching the given query conditions.
1023
+	 *
1024
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1025
+	 * @param int   $limit              How many objects to return.
1026
+	 * @param array $query_params       Any additional conditions on the query.
1027
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1028
+	 *                                  you can indicate just the columns you want returned
1029
+	 * @return array|EE_Base_Class[]
1030
+	 * @throws ReflectionException
1031
+	 * @throws InvalidArgumentException
1032
+	 * @throws InvalidInterfaceException
1033
+	 * @throws InvalidDataTypeException
1034
+	 * @throws EE_Error
1035
+	 */
1036
+	public function previous_x(
1037
+		$field_to_order_by = null,
1038
+		$limit = 1,
1039
+		$query_params = [],
1040
+		$columns_to_select = null
1041
+	) {
1042
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1043
+			? $this->_model->get_primary_key_field()->get_name()
1044
+			: $field_to_order_by;
1045
+		$current_value = ! empty($field) ? $this->get($field) : null;
1046
+		if (empty($field) || empty($current_value)) {
1047
+			return [];
1048
+		}
1049
+		return $this->_model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1050
+	}
1051
+
1052
+
1053
+	/**
1054
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1055
+	 * matching the given query conditions.
1056
+	 *
1057
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1058
+	 * @param array $query_params       Any additional conditions on the query.
1059
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1060
+	 *                                  you can indicate just the columns you want returned
1061
+	 * @return array|EE_Base_Class
1062
+	 * @throws ReflectionException
1063
+	 * @throws InvalidArgumentException
1064
+	 * @throws InvalidInterfaceException
1065
+	 * @throws InvalidDataTypeException
1066
+	 * @throws EE_Error
1067
+	 */
1068
+	public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1069
+	{
1070
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1071
+			? $this->_model->get_primary_key_field()->get_name()
1072
+			: $field_to_order_by;
1073
+		$current_value = ! empty($field) ? $this->get($field) : null;
1074
+		if (empty($field) || empty($current_value)) {
1075
+			return [];
1076
+		}
1077
+		return $this->_model->next($current_value, $field, $query_params, $columns_to_select);
1078
+	}
1079
+
1080
+
1081
+	/**
1082
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1083
+	 * matching the given query conditions.
1084
+	 *
1085
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1086
+	 * @param array $query_params       Any additional conditions on the query.
1087
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1088
+	 *                                  you can indicate just the column you want returned
1089
+	 * @return array|EE_Base_Class
1090
+	 * @throws ReflectionException
1091
+	 * @throws InvalidArgumentException
1092
+	 * @throws InvalidInterfaceException
1093
+	 * @throws InvalidDataTypeException
1094
+	 * @throws EE_Error
1095
+	 */
1096
+	public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1097
+	{
1098
+		$field         = empty($field_to_order_by) && $this->_model->has_primary_key_field()
1099
+			? $this->_model->get_primary_key_field()->get_name()
1100
+			: $field_to_order_by;
1101
+		$current_value = ! empty($field) ? $this->get($field) : null;
1102
+		if (empty($field) || empty($current_value)) {
1103
+			return [];
1104
+		}
1105
+		return $this->_model->previous($current_value, $field, $query_params, $columns_to_select);
1106
+	}
1107
+
1108
+
1109
+	/**
1110
+	 * Overrides parent because parent expects old models.
1111
+	 * This also doesn't do any validation, and won't work for serialized arrays
1112
+	 *
1113
+	 * @param string $field_name
1114
+	 * @param mixed  $field_value_from_db
1115
+	 * @throws InvalidArgumentException
1116
+	 * @throws InvalidInterfaceException
1117
+	 * @throws InvalidDataTypeException
1118
+	 * @throws EE_Error
1119
+	 */
1120
+	public function set_from_db($field_name, $field_value_from_db)
1121
+	{
1122
+		$field_obj = $this->_model->field_settings_for($field_name);
1123
+		if ($field_obj instanceof EE_Model_Field_Base) {
1124
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
1125
+			// eg, a CPT model object could have an entry in the posts table, but no
1126
+			// entry in the meta table. Meaning that all its columns in the meta table
1127
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
1128
+			if ($field_value_from_db === null) {
1129
+				if ($field_obj->is_nullable()) {
1130
+					// if the field allows nulls, then let it be null
1131
+					$field_value = null;
1132
+				} else {
1133
+					$field_value = $field_obj->get_default_value();
1134
+				}
1135
+			} else {
1136
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1137
+			}
1138
+			$this->_fields[ $field_name ] = $field_value;
1139
+			$this->_clear_cached_property($field_name);
1140
+		}
1141
+	}
1142
+
1143
+
1144
+	/**
1145
+	 * verifies that the specified field is of the correct type
1146
+	 *
1147
+	 * @param string $field_name
1148
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1149
+	 *                                (in cases where the same property may be used for different outputs
1150
+	 *                                - i.e. datetime, money etc.)
1151
+	 * @return mixed
1152
+	 * @throws InvalidArgumentException
1153
+	 * @throws InvalidInterfaceException
1154
+	 * @throws InvalidDataTypeException
1155
+	 * @throws EE_Error
1156
+	 */
1157
+	public function get($field_name, $extra_cache_ref = null)
1158
+	{
1159
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1160
+	}
1161
+
1162
+
1163
+	/**
1164
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1165
+	 *
1166
+	 * @param string $field_name A valid field name
1167
+	 * @return mixed              Whatever the raw value stored on the property is.
1168
+	 * @throws InvalidArgumentException
1169
+	 * @throws InvalidInterfaceException
1170
+	 * @throws InvalidDataTypeException
1171
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1172
+	 */
1173
+	public function get_raw($field_name)
1174
+	{
1175
+		$field_settings = $this->_model->field_settings_for($field_name);
1176
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1177
+			? $this->_fields[ $field_name ]->format('U')
1178
+			: $this->_fields[ $field_name ];
1179
+	}
1180
+
1181
+
1182
+	/**
1183
+	 * This is used to return the internal DateTime object used for a field that is a
1184
+	 * EE_Datetime_Field.
1185
+	 *
1186
+	 * @param string $field_name               The field name retrieving the DateTime object.
1187
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1188
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1189
+	 *                                         EE_Datetime_Field and but the field value is null, then
1190
+	 *                                         just null is returned (because that indicates that likely
1191
+	 *                                         this field is nullable).
1192
+	 * @throws InvalidArgumentException
1193
+	 * @throws InvalidDataTypeException
1194
+	 * @throws InvalidInterfaceException
1195
+	 */
1196
+	public function get_DateTime_object($field_name)
1197
+	{
1198
+		$field_settings = $this->_model->field_settings_for($field_name);
1199
+		if (! $field_settings instanceof EE_Datetime_Field) {
1200
+			EE_Error::add_error(
1201
+				sprintf(
1202
+					esc_html__(
1203
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1204
+						'event_espresso'
1205
+					),
1206
+					$field_name
1207
+				),
1208
+				__FILE__,
1209
+				__FUNCTION__,
1210
+				__LINE__
1211
+			);
1212
+			return false;
1213
+		}
1214
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1215
+			? clone $this->_fields[ $field_name ]
1216
+			: null;
1217
+	}
1218
+
1219
+
1220
+	/**
1221
+	 * To be used in template to immediately echo out the value, and format it for output.
1222
+	 * Eg, should call stripslashes and whatnot before echoing
1223
+	 *
1224
+	 * @param string $field_name      the name of the field as it appears in the DB
1225
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1226
+	 *                                (in cases where the same property may be used for different outputs
1227
+	 *                                - i.e. datetime, money etc.)
1228
+	 * @return void
1229
+	 * @throws InvalidArgumentException
1230
+	 * @throws InvalidInterfaceException
1231
+	 * @throws InvalidDataTypeException
1232
+	 * @throws EE_Error
1233
+	 */
1234
+	public function e($field_name, $extra_cache_ref = null)
1235
+	{
1236
+		echo $this->get_pretty($field_name, $extra_cache_ref);
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1242
+	 * can be easily used as the value of form input.
1243
+	 *
1244
+	 * @param string $field_name
1245
+	 * @return void
1246
+	 * @throws InvalidArgumentException
1247
+	 * @throws InvalidInterfaceException
1248
+	 * @throws InvalidDataTypeException
1249
+	 * @throws EE_Error
1250
+	 */
1251
+	public function f($field_name)
1252
+	{
1253
+		$this->e($field_name, 'form_input');
1254
+	}
1255
+
1256
+
1257
+	/**
1258
+	 * Same as `f()` but just returns the value instead of echoing it
1259
+	 *
1260
+	 * @param string $field_name
1261
+	 * @return string
1262
+	 * @throws InvalidArgumentException
1263
+	 * @throws InvalidInterfaceException
1264
+	 * @throws InvalidDataTypeException
1265
+	 * @throws EE_Error
1266
+	 */
1267
+	public function get_f($field_name)
1268
+	{
1269
+		return (string)$this->get_pretty($field_name, 'form_input');
1270
+	}
1271
+
1272
+
1273
+	/**
1274
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1275
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1276
+	 * to see what options are available.
1277
+	 *
1278
+	 * @param string $field_name
1279
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1280
+	 *                                (in cases where the same property may be used for different outputs
1281
+	 *                                - i.e. datetime, money etc.)
1282
+	 * @return mixed
1283
+	 * @throws InvalidArgumentException
1284
+	 * @throws InvalidInterfaceException
1285
+	 * @throws InvalidDataTypeException
1286
+	 * @throws EE_Error
1287
+	 */
1288
+	public function get_pretty($field_name, $extra_cache_ref = null)
1289
+	{
1290
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1291
+	}
1292
+
1293
+
1294
+	/**
1295
+	 * This simply returns the datetime for the given field name
1296
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1297
+	 * (and the equivalent e_date, e_time, e_datetime).
1298
+	 *
1299
+	 * @access   protected
1300
+	 * @param string  $field_name    Field on the instantiated EE_Base_Class child object
1301
+	 * @param string  $date_format   valid datetime format used for date
1302
+	 *                               (if '' then we just use the default on the field,
1303
+	 *                               if NULL we use the last-used format)
1304
+	 * @param string  $time_format   Same as above except this is for time format
1305
+	 * @param string  $date_or_time  if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1306
+	 * @param boolean $echo          Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1307
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1308
+	 *                               if field is not a valid dtt field, or void if echoing
1309
+	 * @throws InvalidArgumentException
1310
+	 * @throws InvalidInterfaceException
1311
+	 * @throws InvalidDataTypeException
1312
+	 * @throws EE_Error
1313
+	 */
1314
+	protected function _get_datetime(
1315
+		$field_name,
1316
+		$date_format = '',
1317
+		$time_format = '',
1318
+		$date_or_time = '',
1319
+		$echo = false
1320
+	) {
1321
+		// clear cached property
1322
+		$this->_clear_cached_property($field_name);
1323
+		// reset format properties because they are used in get()
1324
+		$this->_dt_frmt = $date_format !== '' ? $date_format : $this->_dt_frmt;
1325
+		$this->_tm_frmt = $time_format !== '' ? $time_format : $this->_tm_frmt;
1326
+		if ($echo) {
1327
+			$this->e($field_name, $date_or_time);
1328
+			return '';
1329
+		}
1330
+		return $this->get($field_name, $date_or_time);
1331
+	}
1332
+
1333
+
1334
+	/**
1335
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1336
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1337
+	 * other echoes the pretty value for dtt)
1338
+	 *
1339
+	 * @param string $field_name name of model object datetime field holding the value
1340
+	 * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1341
+	 * @return string            datetime value formatted
1342
+	 * @throws InvalidArgumentException
1343
+	 * @throws InvalidInterfaceException
1344
+	 * @throws InvalidDataTypeException
1345
+	 * @throws EE_Error
1346
+	 */
1347
+	public function get_date($field_name, $format = '')
1348
+	{
1349
+		return $this->_get_datetime($field_name, $format, null, 'D');
1350
+	}
1351
+
1352
+
1353
+	/**
1354
+	 * @param        $field_name
1355
+	 * @param string $format
1356
+	 * @throws InvalidArgumentException
1357
+	 * @throws InvalidInterfaceException
1358
+	 * @throws InvalidDataTypeException
1359
+	 * @throws EE_Error
1360
+	 */
1361
+	public function e_date($field_name, $format = '')
1362
+	{
1363
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1364
+	}
1365
+
1366
+
1367
+	/**
1368
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1369
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1370
+	 * other echoes the pretty value for dtt)
1371
+	 *
1372
+	 * @param string $field_name name of model object datetime field holding the value
1373
+	 * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1374
+	 * @return string             datetime value formatted
1375
+	 * @throws InvalidArgumentException
1376
+	 * @throws InvalidInterfaceException
1377
+	 * @throws InvalidDataTypeException
1378
+	 * @throws EE_Error
1379
+	 */
1380
+	public function get_time($field_name, $format = '')
1381
+	{
1382
+		return $this->_get_datetime($field_name, null, $format, 'T');
1383
+	}
1384
+
1385
+
1386
+	/**
1387
+	 * @param        $field_name
1388
+	 * @param string $format
1389
+	 * @throws InvalidArgumentException
1390
+	 * @throws InvalidInterfaceException
1391
+	 * @throws InvalidDataTypeException
1392
+	 * @throws EE_Error
1393
+	 */
1394
+	public function e_time($field_name, $format = '')
1395
+	{
1396
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1397
+	}
1398
+
1399
+
1400
+	/**
1401
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1402
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1403
+	 * other echoes the pretty value for dtt)
1404
+	 *
1405
+	 * @param string $field_name  name of model object datetime field holding the value
1406
+	 * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1407
+	 * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1408
+	 * @return string             datetime value formatted
1409
+	 * @throws InvalidArgumentException
1410
+	 * @throws InvalidInterfaceException
1411
+	 * @throws InvalidDataTypeException
1412
+	 * @throws EE_Error
1413
+	 */
1414
+	public function get_datetime($field_name, $date_format = '', $time_format = '')
1415
+	{
1416
+		return $this->_get_datetime($field_name, $date_format, $time_format);
1417
+	}
1418
+
1419
+
1420
+	/**
1421
+	 * @param string $field_name
1422
+	 * @param string $date_format
1423
+	 * @param string $time_format
1424
+	 * @throws InvalidArgumentException
1425
+	 * @throws InvalidInterfaceException
1426
+	 * @throws InvalidDataTypeException
1427
+	 * @throws EE_Error
1428
+	 */
1429
+	public function e_datetime($field_name, $date_format = '', $time_format = '')
1430
+	{
1431
+		$this->_get_datetime($field_name, $date_format, $time_format, null, true);
1432
+	}
1433
+
1434
+
1435
+	/**
1436
+	 * Get the i8ln value for a date using the WordPress @param string $field_name The EE_Datetime_Field reference for
1437
+	 *                           the date being retrieved.
1438
+	 *
1439
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1440
+	 *                           on the object will be used.
1441
+	 * @return string Date and time string in set locale or false if no field exists for the given
1442
+	 * @throws InvalidArgumentException
1443
+	 * @throws InvalidInterfaceException
1444
+	 * @throws InvalidDataTypeException
1445
+	 * @throws EE_Error
1446
+	 *                           field name.
1447
+	 * @see date_i18n function.
1448
+	 *
1449
+	 */
1450
+	public function get_i18n_datetime($field_name, $format = '')
1451
+	{
1452
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1453
+		return date_i18n(
1454
+			$format,
1455
+			EEH_DTT_Helper::get_timestamp_with_offset(
1456
+				$this->get_raw($field_name),
1457
+				$this->_timezone
1458
+			)
1459
+		);
1460
+	}
1461
+
1462
+
1463
+	/**
1464
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1465
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1466
+	 * thrown.
1467
+	 *
1468
+	 * @param string $field_name The field name being checked
1469
+	 * @return EE_Datetime_Field
1470
+	 * @throws InvalidArgumentException
1471
+	 * @throws InvalidInterfaceException
1472
+	 * @throws InvalidDataTypeException
1473
+	 * @throws EE_Error
1474
+	 */
1475
+	protected function _get_dtt_field_settings($field_name)
1476
+	{
1477
+		$field = $this->_model->field_settings_for($field_name);
1478
+		// check if field is dtt
1479
+		if ($field instanceof EE_Datetime_Field) {
1480
+			return $field;
1481
+		}
1482
+		throw new EE_Error(
1483
+			sprintf(
1484
+				esc_html__(
1485
+					'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',
1486
+					'event_espresso'
1487
+				),
1488
+				$field_name,
1489
+				self::_get_model_classname(get_class($this))
1490
+			)
1491
+		);
1492
+	}
1493
+
1494
+
1495
+
1496
+
1497
+	/**
1498
+	 * NOTE ABOUT BELOW:
1499
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1500
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1501
+	 * you want to set both date and time at the same time, you can just use the models default set($field_name,$value)
1502
+	 * method and make sure you send the entire datetime value for setting.
1503
+	 */
1504
+	/**
1505
+	 * sets the time on a datetime property
1506
+	 *
1507
+	 * @access protected
1508
+	 * @param string|Datetime $time       a valid time string for php datetime functions (or DateTime object)
1509
+	 * @param string          $field_name the name of the field the time is being set on (must match a
1510
+	 *                                    EE_Datetime_Field)
1511
+	 * @throws InvalidArgumentException
1512
+	 * @throws InvalidInterfaceException
1513
+	 * @throws InvalidDataTypeException
1514
+	 * @throws EE_Error
1515
+	 */
1516
+	protected function _set_time_for($time, $field_name)
1517
+	{
1518
+		$this->_set_date_time('T', $time, $field_name);
1519
+	}
1520
+
1521
+
1522
+	/**
1523
+	 * sets the date on a datetime property
1524
+	 *
1525
+	 * @access protected
1526
+	 * @param string|DateTime $date       a valid date string for php datetime functions ( or DateTime object)
1527
+	 * @param string          $field_name the name of the field the date is being set on (must match a
1528
+	 *                                    EE_Datetime_Field)
1529
+	 * @throws InvalidArgumentException
1530
+	 * @throws InvalidInterfaceException
1531
+	 * @throws InvalidDataTypeException
1532
+	 * @throws EE_Error
1533
+	 */
1534
+	protected function _set_date_for($date, $field_name)
1535
+	{
1536
+		$this->_set_date_time('D', $date, $field_name);
1537
+	}
1538
+
1539
+
1540
+	/**
1541
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1542
+	 * verifies that the given field name matches a model object property and is for a EE_Datetime_Field field
1543
+	 *
1544
+	 * @access protected
1545
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1546
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1547
+	 * @param string          $field_name     the name of the field the date OR time is being set on (must match a
1548
+	 *                                        EE_Datetime_Field property)
1549
+	 * @throws InvalidArgumentException
1550
+	 * @throws InvalidInterfaceException
1551
+	 * @throws InvalidDataTypeException
1552
+	 * @throws EE_Error
1553
+	 */
1554
+	protected function _set_date_time($what, $datetime_value, $field_name)
1555
+	{
1556
+		$field = $this->_get_dtt_field_settings($field_name);
1557
+		$field->set_timezone($this->_timezone);
1558
+		$field->set_date_format($this->_dt_frmt);
1559
+		$field->set_time_format($this->_tm_frmt);
1560
+		$what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
1561
+		switch ($what) {
1562
+			case 'T':
1563
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
1564
+					$datetime_value,
1565
+					$this->_fields[ $field_name ]
1566
+				);
1567
+				$this->_has_changes           = true;
1568
+				break;
1569
+			case 'D':
1570
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
1571
+					$datetime_value,
1572
+					$this->_fields[ $field_name ]
1573
+				);
1574
+				$this->_has_changes           = true;
1575
+				break;
1576
+			case 'B':
1577
+				$this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
1578
+				$this->_has_changes           = true;
1579
+				break;
1580
+		}
1581
+		$this->_clear_cached_property($field_name);
1582
+	}
1583
+
1584
+
1585
+	/**
1586
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1587
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1588
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1589
+	 * that could lead to some unexpected results!
1590
+	 *
1591
+	 * @access public
1592
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1593
+	 *                                         value being returned.
1594
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1595
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1596
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1597
+	 * @param string $append                   You can include something to append on the timestamp
1598
+	 * @return string timestamp
1599
+	 * @throws InvalidArgumentException
1600
+	 * @throws InvalidInterfaceException
1601
+	 * @throws InvalidDataTypeException
1602
+	 * @throws EE_Error
1603
+	 */
1604
+	public function display_in_my_timezone(
1605
+		$field_name,
1606
+		$callback = 'get_datetime',
1607
+		$args = null,
1608
+		$prepend = '',
1609
+		$append = ''
1610
+	) {
1611
+		$timezone = EEH_DTT_Helper::get_timezone();
1612
+		if ($timezone === $this->_timezone) {
1613
+			return '';
1614
+		}
1615
+		$original_timezone = $this->_timezone;
1616
+		$this->set_timezone($timezone);
1617
+		$fn   = (array)$field_name;
1618
+		$args = array_merge($fn, (array)$args);
1619
+		if (! method_exists($this, $callback)) {
1620
+			throw new EE_Error(
1621
+				sprintf(
1622
+					esc_html__(
1623
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1624
+						'event_espresso'
1625
+					),
1626
+					$callback
1627
+				)
1628
+			);
1629
+		}
1630
+		$args   = (array)$args;
1631
+		$return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
1632
+		$this->set_timezone($original_timezone);
1633
+		return $return;
1634
+	}
1635
+
1636
+
1637
+	/**
1638
+	 * Deletes this model object.
1639
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1640
+	 * override
1641
+	 * `EE_Base_Class::_delete` NOT this class.
1642
+	 *
1643
+	 * @return boolean | int
1644
+	 * @throws ReflectionException
1645
+	 * @throws InvalidArgumentException
1646
+	 * @throws InvalidInterfaceException
1647
+	 * @throws InvalidDataTypeException
1648
+	 * @throws EE_Error
1649
+	 */
1650
+	public function delete()
1651
+	{
1652
+		/**
1653
+		 * Called just before the `EE_Base_Class::_delete` method call.
1654
+		 * Note:
1655
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1656
+		 * should be aware that `_delete` may not always result in a permanent delete.
1657
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1658
+		 * soft deletes (trash) the object and does not permanently delete it.
1659
+		 *
1660
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1661
+		 */
1662
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1663
+		$result = $this->_delete();
1664
+		/**
1665
+		 * Called just after the `EE_Base_Class::_delete` method call.
1666
+		 * Note:
1667
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1668
+		 * should be aware that `_delete` may not always result in a permanent delete.
1669
+		 * For example `EE_Soft_Base_Class::_delete`
1670
+		 * soft deletes (trash) the object and does not permanently delete it.
1671
+		 *
1672
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1673
+		 * @param boolean       $result
1674
+		 */
1675
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1676
+		return $result;
1677
+	}
1678
+
1679
+
1680
+	/**
1681
+	 * Calls the specific delete method for the instantiated class.
1682
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1683
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1684
+	 * `EE_Base_Class::delete`
1685
+	 *
1686
+	 * @return bool|int
1687
+	 * @throws ReflectionException
1688
+	 * @throws InvalidArgumentException
1689
+	 * @throws InvalidInterfaceException
1690
+	 * @throws InvalidDataTypeException
1691
+	 * @throws EE_Error
1692
+	 */
1693
+	protected function _delete()
1694
+	{
1695
+		return $this->delete_permanently();
1696
+	}
1697
+
1698
+
1699
+	/**
1700
+	 * Deletes this model object permanently from db
1701
+	 * (but keep in mind related models may block the delete and return an error)
1702
+	 *
1703
+	 * @return bool | int
1704
+	 * @throws ReflectionException
1705
+	 * @throws InvalidArgumentException
1706
+	 * @throws InvalidInterfaceException
1707
+	 * @throws InvalidDataTypeException
1708
+	 * @throws EE_Error
1709
+	 */
1710
+	public function delete_permanently()
1711
+	{
1712
+		/**
1713
+		 * Called just before HARD deleting a model object
1714
+		 *
1715
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1716
+		 */
1717
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1718
+		$result = $this->_model->delete_permanently_by_ID($this->ID());
1719
+		$this->refresh_cache_of_related_objects();
1720
+		/**
1721
+		 * Called just after HARD deleting a model object
1722
+		 *
1723
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1724
+		 * @param boolean       $result
1725
+		 */
1726
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1727
+		return $result;
1728
+	}
1729
+
1730
+
1731
+	/**
1732
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1733
+	 * related model objects
1734
+	 *
1735
+	 * @throws ReflectionException
1736
+	 * @throws InvalidArgumentException
1737
+	 * @throws InvalidInterfaceException
1738
+	 * @throws InvalidDataTypeException
1739
+	 * @throws EE_Error
1740
+	 */
1741
+	public function refresh_cache_of_related_objects()
1742
+	{
1743
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1744
+			if (! empty($this->_model_relations[ $relation_name ])) {
1745
+				$related_objects = $this->_model_relations[ $relation_name ];
1746
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1747
+					// this relation only stores a single model object, not an array
1748
+					// but let's make it consistent
1749
+					$related_objects = [$related_objects];
1750
+				}
1751
+				foreach ($related_objects as $related_object) {
1752
+					// only refresh their cache if they're in memory
1753
+					if ($related_object instanceof EE_Base_Class) {
1754
+						$related_object->clear_cache(
1755
+							$this->_model->get_this_model_name(),
1756
+							$this
1757
+						);
1758
+					}
1759
+				}
1760
+			}
1761
+		}
1762
+	}
1763
+
1764
+
1765
+	/**
1766
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1767
+	 * object just before saving.
1768
+	 *
1769
+	 * @access public
1770
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1771
+	 *                                 if provided during the save() method (often client code will change the fields'
1772
+	 *                                 values before calling save)
1773
+	 * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1774
+	 *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1775
+	 * @throws InvalidInterfaceException
1776
+	 * @throws InvalidDataTypeException
1777
+	 * @throws EE_Error
1778
+	 * @throws InvalidArgumentException
1779
+	 * @throws ReflectionException
1780
+	 * @throws ReflectionException
1781
+	 * @throws ReflectionException
1782
+	 */
1783
+	public function save($set_cols_n_values = [])
1784
+	{
1785
+		/**
1786
+		 * Filters the fields we're about to save on the model object
1787
+		 *
1788
+		 * @param array         $set_cols_n_values
1789
+		 * @param EE_Base_Class $model_object
1790
+		 */
1791
+		$set_cols_n_values = (array)apply_filters(
1792
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1793
+			$set_cols_n_values,
1794
+			$this
1795
+		);
1796
+		// set attributes as provided in $set_cols_n_values
1797
+		foreach ($set_cols_n_values as $column => $value) {
1798
+			$this->set($column, $value);
1799
+		}
1800
+		// no changes ? then don't do anything
1801
+		if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
1802
+			return 0;
1803
+		}
1804
+		/**
1805
+		 * Saving a model object.
1806
+		 * Before we perform a save, this action is fired.
1807
+		 *
1808
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1809
+		 */
1810
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1811
+		if (! $this->allow_persist()) {
1812
+			return 0;
1813
+		}
1814
+		// now get current attribute values
1815
+		$save_cols_n_values = $this->_fields;
1816
+		// if the object already has an ID, update it. Otherwise, insert it
1817
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1818
+		// They have been
1819
+		$old_assumption_concerning_value_preparation = $this->_model
1820
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1821
+		$this->_model->assume_values_already_prepared_by_model_object(true);
1822
+		// does this model have an autoincrement PK?
1823
+		if ($this->_model->has_primary_key_field()) {
1824
+			if ($this->_model->get_primary_key_field()->is_auto_increment()) {
1825
+				// ok check if it's set, if so: update; if not, insert
1826
+				if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
1827
+					$results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
1828
+				} else {
1829
+					unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
1830
+					$results = $this->_model->insert($save_cols_n_values);
1831
+					if ($results) {
1832
+						// if successful, set the primary key
1833
+						// but don't use the normal SET method, because it will check if
1834
+						// an item with the same ID exists in the mapper & db, then
1835
+						// will find it in the db (because we just added it) and THAT object
1836
+						// will get added to the mapper before we can add this one!
1837
+						// but if we just avoid using the SET method, all that headache can be avoided
1838
+						$pk_field_name                   = $this->_model->primary_key_name();
1839
+						$this->_fields[ $pk_field_name ] = $results;
1840
+						$this->_clear_cached_property($pk_field_name);
1841
+						$this->_model->add_to_entity_map($this);
1842
+						$this->_update_cached_related_model_objs_fks();
1843
+					}
1844
+				}
1845
+			} else {// PK is NOT auto-increment
1846
+				// so check if one like it already exists in the db
1847
+				if ($this->_model->exists_by_ID($this->ID())) {
1848
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1849
+						throw new EE_Error(
1850
+							sprintf(
1851
+								esc_html__(
1852
+									'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',
1853
+									'event_espresso'
1854
+								),
1855
+								get_class($this),
1856
+								get_class($this->_model) . '::instance()->add_to_entity_map()',
1857
+								get_class($this->_model) . '::instance()->get_one_by_ID()',
1858
+								'<br />'
1859
+							)
1860
+						);
1861
+					}
1862
+					$results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
1863
+				} else {
1864
+					$results = $this->_model->insert($save_cols_n_values);
1865
+					$this->_update_cached_related_model_objs_fks();
1866
+				}
1867
+			}
1868
+		} else {// there is NO primary key
1869
+			$already_in_db = false;
1870
+			foreach ($this->_model->unique_indexes() as $index) {
1871
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1872
+				if ($this->_model->exists([$uniqueness_where_params])) {
1873
+					$already_in_db = true;
1874
+				}
1875
+			}
1876
+			if ($already_in_db) {
1877
+				$combined_pk_fields_n_values = array_intersect_key(
1878
+					$save_cols_n_values,
1879
+					$this->_model->get_combined_primary_key_fields()
1880
+				);
1881
+				$results                     = $this->_model->update(
1882
+					$save_cols_n_values,
1883
+					$combined_pk_fields_n_values
1884
+				);
1885
+			} else {
1886
+				$results = $this->_model->insert($save_cols_n_values);
1887
+			}
1888
+		}
1889
+		// restore the old assumption about values being prepared by the model object
1890
+		$this->_model->assume_values_already_prepared_by_model_object(
1891
+			$old_assumption_concerning_value_preparation
1892
+		);
1893
+		/**
1894
+		 * After saving the model object this action is called
1895
+		 *
1896
+		 * @param EE_Base_Class $model_object which was just saved
1897
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1898
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1899
+		 */
1900
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1901
+		$this->_has_changes = false;
1902
+		return $results;
1903
+	}
1904
+
1905
+
1906
+	/**
1907
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1908
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1909
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1910
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1911
+	 * 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
1912
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1913
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1914
+	 *
1915
+	 * @return void
1916
+	 * @throws ReflectionException
1917
+	 * @throws InvalidArgumentException
1918
+	 * @throws InvalidInterfaceException
1919
+	 * @throws InvalidDataTypeException
1920
+	 * @throws EE_Error
1921
+	 */
1922
+	protected function _update_cached_related_model_objs_fks()
1923
+	{
1924
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1925
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1926
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1927
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1928
+						$this->_model->get_this_model_name()
1929
+					);
1930
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1931
+					if ($related_model_obj_in_cache->ID()) {
1932
+						$related_model_obj_in_cache->save();
1933
+					}
1934
+				}
1935
+			}
1936
+		}
1937
+	}
1938
+
1939
+
1940
+	/**
1941
+	 * Saves this model object and its NEW cached relations to the database.
1942
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1943
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1944
+	 * because otherwise, there's a potential for infinite looping of saving
1945
+	 * Saves the cached related model objects, and ensures the relation between them
1946
+	 * and this object and properly setup
1947
+	 *
1948
+	 * @return int ID of new model object on save; 0 on failure+
1949
+	 * @throws ReflectionException
1950
+	 * @throws InvalidArgumentException
1951
+	 * @throws InvalidInterfaceException
1952
+	 * @throws InvalidDataTypeException
1953
+	 * @throws EE_Error
1954
+	 */
1955
+	public function save_new_cached_related_model_objs()
1956
+	{
1957
+		// make sure this has been saved
1958
+		if (! $this->ID()) {
1959
+			$id = $this->save();
1960
+		} else {
1961
+			$id = $this->ID();
1962
+		}
1963
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
1964
+		foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
1965
+			if ($this->_model_relations[ $relationName ]) {
1966
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1967
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1968
+				/* @var $related_model_obj EE_Base_Class */
1969
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
1970
+					// add a relation to that relation type (which saves the appropriate thing in the process)
1971
+					// but ONLY if it DOES NOT exist in the DB
1972
+					$related_model_obj = $this->_model_relations[ $relationName ];
1973
+					// if( ! $related_model_obj->ID()){
1974
+					$this->_add_relation_to($related_model_obj, $relationName);
1975
+					$related_model_obj->save_new_cached_related_model_objs();
1976
+					// }
1977
+				} else {
1978
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
1979
+						// add a relation to that relation type (which saves the appropriate thing in the process)
1980
+						// but ONLY if it DOES NOT exist in the DB
1981
+						// if( ! $related_model_obj->ID()){
1982
+						$this->_add_relation_to($related_model_obj, $relationName);
1983
+						$related_model_obj->save_new_cached_related_model_objs();
1984
+						// }
1985
+					}
1986
+				}
1987
+			}
1988
+		}
1989
+		return $id;
1990
+	}
1991
+
1992
+
1993
+	/**
1994
+	 * for getting a model while instantiated.
1995
+	 *
1996
+	 * @return EEM_Base | EEM_CPT_Base
1997
+	 * @throws ReflectionException
1998
+	 * @throws InvalidArgumentException
1999
+	 * @throws InvalidInterfaceException
2000
+	 * @throws InvalidDataTypeException
2001
+	 * @throws EE_Error
2002
+	 */
2003
+	public function get_model()
2004
+	{
2005
+		if (! $this->_model) {
2006
+			$modelName    = self::_get_model_classname(get_class($this));
2007
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2008
+		}
2009
+		return $this->_model;
2010
+	}
2011
+
2012
+
2013
+	/**
2014
+	 * @param $props_n_values
2015
+	 * @param $classname
2016
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2017
+	 * @throws ReflectionException
2018
+	 * @throws InvalidArgumentException
2019
+	 * @throws InvalidInterfaceException
2020
+	 * @throws InvalidDataTypeException
2021
+	 * @throws EE_Error
2022
+	 */
2023
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2024
+	{
2025
+		// TODO: will not work for Term_Relationships because they have no PK!
2026
+		$primary_id_ref = self::_get_primary_key_name($classname);
2027
+		if (
2028
+			array_key_exists($primary_id_ref, $props_n_values)
2029
+			&& ! empty($props_n_values[ $primary_id_ref ])
2030
+		) {
2031
+			$id = $props_n_values[ $primary_id_ref ];
2032
+			return self::_get_model($classname)->get_from_entity_map($id);
2033
+		}
2034
+		return false;
2035
+	}
2036
+
2037
+
2038
+	/**
2039
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2040
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2041
+	 * 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
2042
+	 * we return false.
2043
+	 *
2044
+	 * @param array  $props_n_values    incoming array of properties and their values
2045
+	 * @param string $classname         the classname of the child class
2046
+	 * @param null   $timezone
2047
+	 * @param array  $date_formats      incoming date_formats in an array where the first value is the
2048
+	 *                                  date_format and the second value is the time format
2049
+	 * @return mixed (EE_Base_Class|bool)
2050
+	 * @throws InvalidArgumentException
2051
+	 * @throws InvalidInterfaceException
2052
+	 * @throws InvalidDataTypeException
2053
+	 * @throws EE_Error
2054
+	 * @throws ReflectionException
2055
+	 * @throws ReflectionException
2056
+	 * @throws ReflectionException
2057
+	 */
2058
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = [])
2059
+	{
2060
+		$existing = null;
2061
+		$model    = self::_get_model($classname, $timezone);
2062
+		if ($model->has_primary_key_field()) {
2063
+			$primary_id_ref = self::_get_primary_key_name($classname);
2064
+			if (
2065
+				array_key_exists($primary_id_ref, $props_n_values)
2066
+				&& ! empty($props_n_values[ $primary_id_ref ])
2067
+			) {
2068
+				$existing = $model->get_one_by_ID(
2069
+					$props_n_values[ $primary_id_ref ]
2070
+				);
2071
+			}
2072
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2073
+			// no primary key on this model, but there's still a matching item in the DB
2074
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2075
+				self::_get_model($classname, $timezone)
2076
+					->get_index_primary_key_string($props_n_values)
2077
+			);
2078
+		}
2079
+		if ($existing) {
2080
+			// set date formats if present before setting values
2081
+			if (! empty($date_formats) && is_array($date_formats)) {
2082
+				$existing->set_date_format($date_formats[0]);
2083
+				$existing->set_time_format($date_formats[1]);
2084
+			} else {
2085
+				// set default formats for date and time
2086
+				$existing->set_date_format(get_option('date_format'));
2087
+				$existing->set_time_format(get_option('time_format'));
2088
+			}
2089
+			foreach ($props_n_values as $property => $field_value) {
2090
+				$existing->set($property, $field_value);
2091
+			}
2092
+			return $existing;
2093
+		}
2094
+		return false;
2095
+	}
2096
+
2097
+
2098
+	/**
2099
+	 * Gets the EEM_*_Model for this class
2100
+	 *
2101
+	 * @access public now, as this is more convenient
2102
+	 * @param      $classname
2103
+	 * @param null $timezone
2104
+	 * @return EEM_Base
2105
+	 * @throws InvalidArgumentException
2106
+	 * @throws InvalidInterfaceException
2107
+	 * @throws InvalidDataTypeException
2108
+	 * @throws EE_Error
2109
+	 * @throws ReflectionException
2110
+	 */
2111
+	protected static function _get_model($classname, $timezone = null)
2112
+	{
2113
+		// find model for this class
2114
+		if (! $classname) {
2115
+			throw new EE_Error(
2116
+				sprintf(
2117
+					esc_html__(
2118
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2119
+						'event_espresso'
2120
+					),
2121
+					$classname
2122
+				)
2123
+			);
2124
+		}
2125
+		$modelName = self::_get_model_classname($classname);
2126
+		return self::_get_model_instance_with_name($modelName, $timezone);
2127
+	}
2128
+
2129
+
2130
+	/**
2131
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2132
+	 *
2133
+	 * @param string $model_classname
2134
+	 * @param null   $timezone
2135
+	 * @return EEM_Base
2136
+	 * @throws ReflectionException
2137
+	 * @throws InvalidArgumentException
2138
+	 * @throws InvalidInterfaceException
2139
+	 * @throws InvalidDataTypeException
2140
+	 * @throws EE_Error
2141
+	 */
2142
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2143
+	{
2144
+		$model_classname = str_replace('EEM_', '', $model_classname);
2145
+		$model           = EE_Registry::instance()->load_model($model_classname);
2146
+		$model->set_timezone($timezone);
2147
+		return $model;
2148
+	}
2149
+
2150
+
2151
+	/**
2152
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2153
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2154
+	 *
2155
+	 * @param null $model_name
2156
+	 * @return string like EEM_Attendee
2157
+	 */
2158
+	private static function _get_model_classname($model_name = null)
2159
+	{
2160
+		if (strpos($model_name, 'EE_') === 0) {
2161
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
2162
+		} else {
2163
+			$model_classname = 'EEM_' . $model_name;
2164
+		}
2165
+		return $model_classname;
2166
+	}
2167
+
2168
+
2169
+	/**
2170
+	 * returns the name of the primary key attribute
2171
+	 *
2172
+	 * @param null $classname
2173
+	 * @return string
2174
+	 * @throws InvalidArgumentException
2175
+	 * @throws InvalidInterfaceException
2176
+	 * @throws InvalidDataTypeException
2177
+	 * @throws EE_Error
2178
+	 * @throws ReflectionException
2179
+	 */
2180
+	protected static function _get_primary_key_name($classname = null)
2181
+	{
2182
+		if (! $classname) {
2183
+			throw new EE_Error(
2184
+				sprintf(
2185
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2186
+					$classname
2187
+				)
2188
+			);
2189
+		}
2190
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2191
+	}
2192
+
2193
+
2194
+	/**
2195
+	 * Gets the value of the primary key.
2196
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2197
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2198
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2199
+	 *
2200
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2201
+	 * @throws InvalidArgumentException
2202
+	 * @throws InvalidInterfaceException
2203
+	 * @throws InvalidDataTypeException
2204
+	 * @throws EE_Error
2205
+	 */
2206
+	public function ID()
2207
+	{
2208
+		// now that we know the name of the variable, use a variable variable to get its value and return its
2209
+		if ($this->_model->has_primary_key_field()) {
2210
+			return $this->_fields[ $this->_model->primary_key_name() ];
2211
+		}
2212
+		return $this->_model->get_index_primary_key_string($this->_fields);
2213
+	}
2214
+
2215
+
2216
+	/**
2217
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2218
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2219
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2220
+	 *
2221
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2222
+	 * @param string $relationName                     eg 'Events','Question',etc.
2223
+	 *                                                 an attendee to a group, you also want to specify which role they
2224
+	 *                                                 will have in that group. So you would use this parameter to
2225
+	 *                                                 specify array('role-column-name'=>'role-id')
2226
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2227
+	 *                                                 allow you to further constrict the relation to being added.
2228
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2229
+	 *                                                 column on the JOIN table and currently only the HABTM models
2230
+	 *                                                 accept these additional conditions.  Also remember that if an
2231
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2232
+	 *                                                 NEW row is created in the join table.
2233
+	 * @param null   $cache_id
2234
+	 * @return EE_Base_Class the object the relation was added to
2235
+	 * @throws ReflectionException
2236
+	 * @throws InvalidArgumentException
2237
+	 * @throws InvalidInterfaceException
2238
+	 * @throws InvalidDataTypeException
2239
+	 * @throws EE_Error
2240
+	 */
2241
+	public function _add_relation_to(
2242
+		$otherObjectModelObjectOrID,
2243
+		$relationName,
2244
+		$extra_join_model_fields_n_values = [],
2245
+		$cache_id = null
2246
+	) {
2247
+		// if this thing exists in the DB, save the relation to the DB
2248
+		if ($this->ID()) {
2249
+			$otherObject = $this->_model->add_relationship_to(
2250
+				$this,
2251
+				$otherObjectModelObjectOrID,
2252
+				$relationName,
2253
+				$extra_join_model_fields_n_values
2254
+			);
2255
+			// clear cache so future get_many_related and get_first_related() return new results.
2256
+			$this->clear_cache($relationName, $otherObject, true);
2257
+			if ($otherObject instanceof EE_Base_Class) {
2258
+				$otherObject->clear_cache($this->_model->get_this_model_name(), $this);
2259
+			}
2260
+		} else {
2261
+			// this thing doesn't exist in the DB,  so just cache it
2262
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2263
+				throw new EE_Error(
2264
+					sprintf(
2265
+						esc_html__(
2266
+							'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',
2267
+							'event_espresso'
2268
+						),
2269
+						$otherObjectModelObjectOrID,
2270
+						get_class($this)
2271
+					)
2272
+				);
2273
+			}
2274
+			$otherObject = $otherObjectModelObjectOrID;
2275
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2276
+		}
2277
+		if ($otherObject instanceof EE_Base_Class) {
2278
+			// fix the reciprocal relation too
2279
+			if ($otherObject->ID()) {
2280
+				// its saved so assumed relations exist in the DB, so we can just
2281
+				// clear the cache so future queries use the updated info in the DB
2282
+				$otherObject->clear_cache(
2283
+					$this->_model->get_this_model_name(),
2284
+					null,
2285
+					true
2286
+				);
2287
+			} else {
2288
+				// it's not saved, so it caches relations like this
2289
+				$otherObject->cache($this->_model->get_this_model_name(), $this);
2290
+			}
2291
+		}
2292
+		return $otherObject;
2293
+	}
2294
+
2295
+
2296
+	/**
2297
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2298
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2299
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2300
+	 * from the cache
2301
+	 *
2302
+	 * @param mixed  $otherObjectModelObjectOrID
2303
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2304
+	 *                to the DB yet
2305
+	 * @param string $relationName
2306
+	 * @param array  $where_query
2307
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2308
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2309
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2310
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2311
+	 *                deleted.
2312
+	 * @return EE_Base_Class the relation was removed from
2313
+	 * @throws ReflectionException
2314
+	 * @throws InvalidArgumentException
2315
+	 * @throws InvalidInterfaceException
2316
+	 * @throws InvalidDataTypeException
2317
+	 * @throws EE_Error
2318
+	 */
2319
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
2320
+	{
2321
+		if ($this->ID()) {
2322
+			// if this exists in the DB, save the relation change to the DB too
2323
+			$otherObject = $this->_model->remove_relationship_to(
2324
+				$this,
2325
+				$otherObjectModelObjectOrID,
2326
+				$relationName,
2327
+				$where_query
2328
+			);
2329
+			$this->clear_cache(
2330
+				$relationName,
2331
+				$otherObject
2332
+			);
2333
+		} else {
2334
+			// this doesn't exist in the DB, just remove it from the cache
2335
+			$otherObject = $this->clear_cache(
2336
+				$relationName,
2337
+				$otherObjectModelObjectOrID
2338
+			);
2339
+		}
2340
+		if ($otherObject instanceof EE_Base_Class) {
2341
+			$otherObject->clear_cache(
2342
+				$this->_model->get_this_model_name(),
2343
+				$this
2344
+			);
2345
+		}
2346
+		return $otherObject;
2347
+	}
2348
+
2349
+
2350
+	/**
2351
+	 * Removes ALL the related things for the $relationName.
2352
+	 *
2353
+	 * @param string $relationName
2354
+	 * @param array  $where_query_params @see
2355
+	 *                                   https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2356
+	 * @return EE_Base_Class
2357
+	 * @throws ReflectionException
2358
+	 * @throws InvalidArgumentException
2359
+	 * @throws InvalidInterfaceException
2360
+	 * @throws InvalidDataTypeException
2361
+	 * @throws EE_Error
2362
+	 */
2363
+	public function _remove_relations($relationName, $where_query_params = [])
2364
+	{
2365
+		if ($this->ID()) {
2366
+			// if this exists in the DB, save the relation change to the DB too
2367
+			$otherObjects = $this->_model->remove_relations(
2368
+				$this,
2369
+				$relationName,
2370
+				$where_query_params
2371
+			);
2372
+			$this->clear_cache(
2373
+				$relationName,
2374
+				null,
2375
+				true
2376
+			);
2377
+		} else {
2378
+			// this doesn't exist in the DB, just remove it from the cache
2379
+			$otherObjects = $this->clear_cache(
2380
+				$relationName,
2381
+				null,
2382
+				true
2383
+			);
2384
+		}
2385
+		if (is_array($otherObjects)) {
2386
+			foreach ($otherObjects as $otherObject) {
2387
+				$otherObject->clear_cache(
2388
+					$this->_model->get_this_model_name(),
2389
+					$this
2390
+				);
2391
+			}
2392
+		}
2393
+		return $otherObjects;
2394
+	}
2395
+
2396
+
2397
+	/**
2398
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2399
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2400
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2401
+	 * because we want to get even deleted items etc.
2402
+	 *
2403
+	 * @param string $relationName key in the model's _model_relations array
2404
+	 * @param array  $query_params @see
2405
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2406
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2407
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2408
+	 *                             results if you want IDs
2409
+	 * @throws ReflectionException
2410
+	 * @throws InvalidArgumentException
2411
+	 * @throws InvalidInterfaceException
2412
+	 * @throws InvalidDataTypeException
2413
+	 * @throws EE_Error
2414
+	 */
2415
+	public function get_many_related($relationName, $query_params = [])
2416
+	{
2417
+		if ($this->ID()) {
2418
+			// this exists in the DB, so get the related things from either the cache or the DB
2419
+			// if there are query parameters, forget about caching the related model objects.
2420
+			if ($query_params) {
2421
+				$related_model_objects = $this->_model->get_all_related(
2422
+					$this,
2423
+					$relationName,
2424
+					$query_params
2425
+				);
2426
+			} else {
2427
+				// did we already cache the result of this query?
2428
+				$cached_results = $this->get_all_from_cache($relationName);
2429
+				if (! $cached_results) {
2430
+					$related_model_objects = $this->_model->get_all_related(
2431
+						$this,
2432
+						$relationName,
2433
+						$query_params
2434
+					);
2435
+					// if no query parameters were passed, then we got all the related model objects
2436
+					// for that relation. We can cache them then.
2437
+					foreach ($related_model_objects as $related_model_object) {
2438
+						$this->cache($relationName, $related_model_object);
2439
+					}
2440
+				} else {
2441
+					$related_model_objects = $cached_results;
2442
+				}
2443
+			}
2444
+		} else {
2445
+			// this doesn't exist in the DB, so just get the related things from the cache
2446
+			$related_model_objects = $this->get_all_from_cache($relationName);
2447
+		}
2448
+		return $related_model_objects;
2449
+	}
2450
+
2451
+
2452
+	/**
2453
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2454
+	 * unless otherwise specified in the $query_params
2455
+	 *
2456
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2457
+	 * @param array  $query_params   @see
2458
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2459
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2460
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2461
+	 *                               that by the setting $distinct to TRUE;
2462
+	 * @return int
2463
+	 * @throws ReflectionException
2464
+	 * @throws InvalidArgumentException
2465
+	 * @throws InvalidInterfaceException
2466
+	 * @throws InvalidDataTypeException
2467
+	 * @throws EE_Error
2468
+	 */
2469
+	public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2470
+	{
2471
+		return $this->_model->count_related(
2472
+			$this,
2473
+			$relation_name,
2474
+			$query_params,
2475
+			$field_to_count,
2476
+			$distinct
2477
+		);
2478
+	}
2479
+
2480
+
2481
+	/**
2482
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2483
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2484
+	 *
2485
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2486
+	 * @param array  $query_params  @see
2487
+	 *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2488
+	 * @param string $field_to_sum  name of field to count by.
2489
+	 *                              By default, uses primary key
2490
+	 *                              (which doesn't make much sense, so you should probably change it)
2491
+	 * @return int
2492
+	 * @throws ReflectionException
2493
+	 * @throws InvalidArgumentException
2494
+	 * @throws InvalidInterfaceException
2495
+	 * @throws InvalidDataTypeException
2496
+	 * @throws EE_Error
2497
+	 */
2498
+	public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2499
+	{
2500
+		return $this->_model->sum_related(
2501
+			$this,
2502
+			$relation_name,
2503
+			$query_params,
2504
+			$field_to_sum
2505
+		);
2506
+	}
2507
+
2508
+
2509
+	/**
2510
+	 * Gets the first (ie, one) related model object of the specified type.
2511
+	 *
2512
+	 * @param string $relationName key in the model's _model_relations array
2513
+	 * @param array  $query_params @see
2514
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2515
+	 * @return EE_Base_Class (not an array, a single object)
2516
+	 * @throws ReflectionException
2517
+	 * @throws InvalidArgumentException
2518
+	 * @throws InvalidInterfaceException
2519
+	 * @throws InvalidDataTypeException
2520
+	 * @throws EE_Error
2521
+	 */
2522
+	public function get_first_related($relationName, $query_params = [])
2523
+	{
2524
+		$model_relation = $this->_model->related_settings_for($relationName);
2525
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2526
+			// if they've provided some query parameters, don't bother trying to cache the result
2527
+			// also make sure we're not caching the result of get_first_related
2528
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
2529
+			if (
2530
+				$query_params
2531
+				|| ! $model_relation instanceof EE_Belongs_To_Relation
2532
+			) {
2533
+				$related_model_object = $this->_model->get_first_related(
2534
+					$this,
2535
+					$relationName,
2536
+					$query_params
2537
+				);
2538
+			} else {
2539
+				// first, check if we've already cached the result of this query
2540
+				$cached_result = $this->get_one_from_cache($relationName);
2541
+				if (! $cached_result) {
2542
+					$related_model_object = $this->_model->get_first_related(
2543
+						$this,
2544
+						$relationName,
2545
+						$query_params
2546
+					);
2547
+					$this->cache($relationName, $related_model_object);
2548
+				} else {
2549
+					$related_model_object = $cached_result;
2550
+				}
2551
+			}
2552
+		} else {
2553
+			$related_model_object = null;
2554
+			// this doesn't exist in the Db,
2555
+			// but maybe the relation is of type belongs to, and so the related thing might
2556
+			if ($model_relation instanceof EE_Belongs_To_Relation) {
2557
+				$related_model_object = $this->_model->get_first_related(
2558
+					$this,
2559
+					$relationName,
2560
+					$query_params
2561
+				);
2562
+			}
2563
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2564
+			// just get what's cached on this object
2565
+			if (! $related_model_object) {
2566
+				$related_model_object = $this->get_one_from_cache($relationName);
2567
+			}
2568
+		}
2569
+		return $related_model_object;
2570
+	}
2571
+
2572
+
2573
+	/**
2574
+	 * Does a delete on all related objects of type $relationName and removes
2575
+	 * the current model object's relation to them. If they can't be deleted (because
2576
+	 * of blocking related model objects) does nothing. If the related model objects are
2577
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2578
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2579
+	 *
2580
+	 * @param string $relationName
2581
+	 * @param array  $query_params @see
2582
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2583
+	 * @return int how many deleted
2584
+	 * @throws ReflectionException
2585
+	 * @throws InvalidArgumentException
2586
+	 * @throws InvalidInterfaceException
2587
+	 * @throws InvalidDataTypeException
2588
+	 * @throws EE_Error
2589
+	 */
2590
+	public function delete_related($relationName, $query_params = [])
2591
+	{
2592
+		if ($this->ID()) {
2593
+			$count = $this->_model->delete_related(
2594
+				$this,
2595
+				$relationName,
2596
+				$query_params
2597
+			);
2598
+		} else {
2599
+			$count = count($this->get_all_from_cache($relationName));
2600
+			$this->clear_cache($relationName, null, true);
2601
+		}
2602
+		return $count;
2603
+	}
2604
+
2605
+
2606
+	/**
2607
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2608
+	 * the current model object's relation to them. If they can't be deleted (because
2609
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2610
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2611
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2612
+	 *
2613
+	 * @param string $relationName
2614
+	 * @param array  $query_params @see
2615
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2616
+	 * @return int how many deleted (including those soft deleted)
2617
+	 * @throws ReflectionException
2618
+	 * @throws InvalidArgumentException
2619
+	 * @throws InvalidInterfaceException
2620
+	 * @throws InvalidDataTypeException
2621
+	 * @throws EE_Error
2622
+	 */
2623
+	public function delete_related_permanently($relationName, $query_params = [])
2624
+	{
2625
+		if ($this->ID()) {
2626
+			$count = $this->_model->delete_related_permanently(
2627
+				$this,
2628
+				$relationName,
2629
+				$query_params
2630
+			);
2631
+		} else {
2632
+			$count = count($this->get_all_from_cache($relationName));
2633
+		}
2634
+		$this->clear_cache($relationName, null, true);
2635
+		return $count;
2636
+	}
2637
+
2638
+
2639
+	/**
2640
+	 * is_set
2641
+	 * Just a simple utility function children can use for checking if property exists
2642
+	 *
2643
+	 * @access  public
2644
+	 * @param string $field_name property to check
2645
+	 * @return bool                              TRUE if existing,FALSE if not.
2646
+	 */
2647
+	public function is_set($field_name)
2648
+	{
2649
+		return isset($this->_fields[ $field_name ]);
2650
+	}
2651
+
2652
+
2653
+	/**
2654
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2655
+	 * EE_Error exception if they don't
2656
+	 *
2657
+	 * @param mixed (string|array) $properties properties to check
2658
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2659
+	 * @throws EE_Error
2660
+	 */
2661
+	protected function _property_exists($properties)
2662
+	{
2663
+		foreach ((array)$properties as $property_name) {
2664
+			// first make sure this property exists
2665
+			if (! $this->_fields[ $property_name ]) {
2666
+				throw new EE_Error(
2667
+					sprintf(
2668
+						esc_html__(
2669
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2670
+							'event_espresso'
2671
+						),
2672
+						$property_name
2673
+					)
2674
+				);
2675
+			}
2676
+		}
2677
+		return true;
2678
+	}
2679
+
2680
+
2681
+	/**
2682
+	 * This simply returns an array of model fields for this object
2683
+	 *
2684
+	 * @return array
2685
+	 * @throws InvalidArgumentException
2686
+	 * @throws InvalidInterfaceException
2687
+	 * @throws InvalidDataTypeException
2688
+	 * @throws EE_Error
2689
+	 */
2690
+	public function model_field_array()
2691
+	{
2692
+		$fields     = $this->_model->field_settings();
2693
+		$properties = [];
2694
+		// remove prepended underscore
2695
+		foreach ($fields as $field_name => $settings) {
2696
+			$properties[ $field_name ] = $this->get($field_name);
2697
+		}
2698
+		return $properties;
2699
+	}
2700
+
2701
+
2702
+	/**
2703
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2704
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2705
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2706
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2707
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2708
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2709
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2710
+	 * and accepts 2 arguments: the object on which the function was called,
2711
+	 * and an array of the original arguments passed to the function.
2712
+	 * Whatever their callback function returns will be returned by this function.
2713
+	 * Example: in functions.php (or in a plugin):
2714
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2715
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2716
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2717
+	 *          return $previousReturnValue.$returnString;
2718
+	 *      }
2719
+	 * require('EE_Answer.class.php');
2720
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2721
+	 * echo $answer->my_callback('monkeys',100);
2722
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2723
+	 *
2724
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2725
+	 * @param array  $args       array of original arguments passed to the function
2726
+	 * @return mixed whatever the plugin which calls add_filter decides
2727
+	 * @throws EE_Error
2728
+	 */
2729
+	public function __call($methodName, $args)
2730
+	{
2731
+		$className = get_class($this);
2732
+		$tagName   = "FHEE__{$className}__{$methodName}";
2733
+		if (! has_filter($tagName)) {
2734
+			throw new EE_Error(
2735
+				sprintf(
2736
+					esc_html__(
2737
+						"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;}",
2738
+						'event_espresso'
2739
+					),
2740
+					$methodName,
2741
+					$className,
2742
+					$tagName
2743
+				)
2744
+			);
2745
+		}
2746
+		return apply_filters($tagName, null, $this, $args);
2747
+	}
2748
+
2749
+
2750
+	/**
2751
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2752
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2753
+	 *
2754
+	 * @param string $meta_key
2755
+	 * @param mixed  $meta_value
2756
+	 * @param mixed  $previous_value
2757
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2758
+	 *                  NOTE: if the values haven't changed, returns 0
2759
+	 * @throws InvalidArgumentException
2760
+	 * @throws InvalidInterfaceException
2761
+	 * @throws InvalidDataTypeException
2762
+	 * @throws EE_Error
2763
+	 * @throws ReflectionException
2764
+	 */
2765
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2766
+	{
2767
+		$query_params = [
2768
+			[
2769
+				'EXM_key'  => $meta_key,
2770
+				'OBJ_ID'   => $this->ID(),
2771
+				'EXM_type' => $this->_model->get_this_model_name(),
2772
+			],
2773
+		];
2774
+		if ($previous_value !== null) {
2775
+			$query_params[0]['EXM_value'] = $meta_value;
2776
+		}
2777
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2778
+		if (! $existing_rows_like_that) {
2779
+			return $this->add_extra_meta($meta_key, $meta_value);
2780
+		}
2781
+		foreach ($existing_rows_like_that as $existing_row) {
2782
+			$existing_row->save(['EXM_value' => $meta_value]);
2783
+		}
2784
+		return count($existing_rows_like_that);
2785
+	}
2786
+
2787
+
2788
+	/**
2789
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2790
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2791
+	 * extra meta row was entered, false if not
2792
+	 *
2793
+	 * @param string  $meta_key
2794
+	 * @param mixed   $meta_value
2795
+	 * @param boolean $unique
2796
+	 * @return boolean
2797
+	 * @throws InvalidArgumentException
2798
+	 * @throws InvalidInterfaceException
2799
+	 * @throws InvalidDataTypeException
2800
+	 * @throws EE_Error
2801
+	 * @throws ReflectionException
2802
+	 * @throws ReflectionException
2803
+	 */
2804
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2805
+	{
2806
+		if ($unique) {
2807
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2808
+				[
2809
+					[
2810
+						'EXM_key'  => $meta_key,
2811
+						'OBJ_ID'   => $this->ID(),
2812
+						'EXM_type' => $this->_model->get_this_model_name(),
2813
+					],
2814
+				]
2815
+			);
2816
+			if ($existing_extra_meta) {
2817
+				return false;
2818
+			}
2819
+		}
2820
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2821
+			[
2822
+				'EXM_key'   => $meta_key,
2823
+				'EXM_value' => $meta_value,
2824
+				'OBJ_ID'    => $this->ID(),
2825
+				'EXM_type'  => $this->_model->get_this_model_name(),
2826
+			]
2827
+		);
2828
+		$new_extra_meta->save();
2829
+		return true;
2830
+	}
2831
+
2832
+
2833
+	/**
2834
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2835
+	 * is specified, only deletes extra meta records with that value.
2836
+	 *
2837
+	 * @param string $meta_key
2838
+	 * @param mixed  $meta_value
2839
+	 * @return int number of extra meta rows deleted
2840
+	 * @throws InvalidArgumentException
2841
+	 * @throws InvalidInterfaceException
2842
+	 * @throws InvalidDataTypeException
2843
+	 * @throws EE_Error
2844
+	 * @throws ReflectionException
2845
+	 */
2846
+	public function delete_extra_meta($meta_key, $meta_value = null)
2847
+	{
2848
+		$query_params = [
2849
+			[
2850
+				'EXM_key'  => $meta_key,
2851
+				'OBJ_ID'   => $this->ID(),
2852
+				'EXM_type' => $this->_model->get_this_model_name(),
2853
+			],
2854
+		];
2855
+		if ($meta_value !== null) {
2856
+			$query_params[0]['EXM_value'] = $meta_value;
2857
+		}
2858
+		return EEM_Extra_Meta::instance()->delete($query_params);
2859
+	}
2860
+
2861
+
2862
+	/**
2863
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2864
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2865
+	 * You can specify $default is case you haven't found the extra meta
2866
+	 *
2867
+	 * @param string  $meta_key
2868
+	 * @param boolean $single
2869
+	 * @param mixed   $default if we don't find anything, what should we return?
2870
+	 * @return mixed single value if $single; array if ! $single
2871
+	 * @throws ReflectionException
2872
+	 * @throws InvalidArgumentException
2873
+	 * @throws InvalidInterfaceException
2874
+	 * @throws InvalidDataTypeException
2875
+	 * @throws EE_Error
2876
+	 */
2877
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2878
+	{
2879
+		if ($single) {
2880
+			$result = $this->get_first_related(
2881
+				'Extra_Meta',
2882
+				[['EXM_key' => $meta_key]]
2883
+			);
2884
+			if ($result instanceof EE_Extra_Meta) {
2885
+				return $result->value();
2886
+			}
2887
+		} else {
2888
+			$results = $this->get_many_related(
2889
+				'Extra_Meta',
2890
+				[['EXM_key' => $meta_key]]
2891
+			);
2892
+			if ($results) {
2893
+				$values = [];
2894
+				foreach ($results as $result) {
2895
+					if ($result instanceof EE_Extra_Meta) {
2896
+						$values[ $result->ID() ] = $result->value();
2897
+					}
2898
+				}
2899
+				return $values;
2900
+			}
2901
+		}
2902
+		// if nothing discovered yet return default.
2903
+		return apply_filters(
2904
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2905
+			$default,
2906
+			$meta_key,
2907
+			$single,
2908
+			$this
2909
+		);
2910
+	}
2911
+
2912
+
2913
+	/**
2914
+	 * Returns a simple array of all the extra meta associated with this model object.
2915
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2916
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2917
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2918
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2919
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2920
+	 * finally the extra meta's value as each sub-value. (eg
2921
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2922
+	 *
2923
+	 * @param boolean $one_of_each_key
2924
+	 * @return array
2925
+	 * @throws ReflectionException
2926
+	 * @throws InvalidArgumentException
2927
+	 * @throws InvalidInterfaceException
2928
+	 * @throws InvalidDataTypeException
2929
+	 * @throws EE_Error
2930
+	 */
2931
+	public function all_extra_meta_array($one_of_each_key = true)
2932
+	{
2933
+		$return_array = [];
2934
+		if ($one_of_each_key) {
2935
+			$extra_meta_objs = $this->get_many_related(
2936
+				'Extra_Meta',
2937
+				['group_by' => 'EXM_key']
2938
+			);
2939
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2940
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2941
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2942
+				}
2943
+			}
2944
+		} else {
2945
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2946
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2947
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2948
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2949
+						$return_array[ $extra_meta_obj->key() ] = [];
2950
+					}
2951
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2952
+				}
2953
+			}
2954
+		}
2955
+		return $return_array;
2956
+	}
2957
+
2958
+
2959
+	/**
2960
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
2961
+	 *
2962
+	 * @return string
2963
+	 * @throws InvalidArgumentException
2964
+	 * @throws InvalidInterfaceException
2965
+	 * @throws InvalidDataTypeException
2966
+	 * @throws EE_Error
2967
+	 */
2968
+	public function name()
2969
+	{
2970
+		// find a field that's not a text field
2971
+		$field_we_can_use = $this->_model->get_a_field_of_type('EE_Text_Field_Base');
2972
+		if ($field_we_can_use) {
2973
+			return $this->get($field_we_can_use->get_name());
2974
+		}
2975
+		$first_few_properties = $this->model_field_array();
2976
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
2977
+		$name_parts           = [];
2978
+		foreach ($first_few_properties as $name => $value) {
2979
+			$name_parts[] = "$name:$value";
2980
+		}
2981
+		return implode(',', $name_parts);
2982
+	}
2983
+
2984
+
2985
+	/**
2986
+	 * in_entity_map
2987
+	 * Checks if this model object has been proven to already be in the entity map
2988
+	 *
2989
+	 * @return boolean
2990
+	 * @throws InvalidArgumentException
2991
+	 * @throws InvalidInterfaceException
2992
+	 * @throws InvalidDataTypeException
2993
+	 * @throws EE_Error
2994
+	 */
2995
+	public function in_entity_map()
2996
+	{
2997
+		// well, if we looked, did we find it in the entity map?
2998
+		return $this->ID() && $this->_model->get_from_entity_map($this->ID()) === $this;
2999
+	}
3000
+
3001
+
3002
+	/**
3003
+	 * refresh_from_db
3004
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3005
+	 *
3006
+	 * @throws ReflectionException
3007
+	 * @throws InvalidArgumentException
3008
+	 * @throws InvalidInterfaceException
3009
+	 * @throws InvalidDataTypeException
3010
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3011
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3012
+	 */
3013
+	public function refresh_from_db()
3014
+	{
3015
+		if ($this->ID() && $this->in_entity_map()) {
3016
+			$this->_model->refresh_entity_map_from_db($this->ID());
3017
+		} else {
3018
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3019
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
3020
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3021
+			// absolutely nothing in it for this ID
3022
+			if (WP_DEBUG) {
3023
+				throw new EE_Error(
3024
+					sprintf(
3025
+						esc_html__(
3026
+							'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.',
3027
+							'event_espresso'
3028
+						),
3029
+						$this->ID(),
3030
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3031
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3032
+					)
3033
+				);
3034
+			}
3035
+		}
3036
+	}
3037
+
3038
+
3039
+	/**
3040
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3041
+	 *
3042
+	 * @param EE_Model_Field_Base[] $fields
3043
+	 * @param string                $new_value_sql
3044
+	 *          example: 'column_name=123',
3045
+	 *          or 'column_name=column_name+1',
3046
+	 *          or 'column_name= CASE
3047
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3048
+	 *          THEN `column_name` + 5
3049
+	 *          ELSE `column_name`
3050
+	 *          END'
3051
+	 *          Also updates $field on this model object with the latest value from the database.
3052
+	 * @return bool
3053
+	 * @throws EE_Error
3054
+	 * @throws InvalidArgumentException
3055
+	 * @throws InvalidDataTypeException
3056
+	 * @throws InvalidInterfaceException
3057
+	 * @throws ReflectionException
3058
+	 * @since 4.9.80.p
3059
+	 */
3060
+	protected function updateFieldsInDB($fields, $new_value_sql)
3061
+	{
3062
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3063
+		// if it wasn't even there to start off.
3064
+		if (! $this->ID()) {
3065
+			$this->save();
3066
+		}
3067
+		global $wpdb;
3068
+		if (empty($fields)) {
3069
+			throw new InvalidArgumentException(
3070
+				esc_html__(
3071
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3072
+					'event_espresso'
3073
+				)
3074
+			);
3075
+		}
3076
+		$first_field = reset($fields);
3077
+		$table_alias = $first_field->get_table_alias();
3078
+		foreach ($fields as $field) {
3079
+			if ($table_alias !== $field->get_table_alias()) {
3080
+				throw new InvalidArgumentException(
3081
+					sprintf(
3082
+						esc_html__(
3083
+						// @codingStandardsIgnoreStart
3084
+							'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.',
3085
+							// @codingStandardsIgnoreEnd
3086
+							'event_espresso'
3087
+						),
3088
+						$table_alias,
3089
+						$field->get_table_alias()
3090
+					)
3091
+				);
3092
+			}
3093
+		}
3094
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3095
+		$table_obj      = $this->_model->get_table_obj_by_alias($table_alias);
3096
+		$table_pk_value = $this->ID();
3097
+		$table_name     = $table_obj->get_table_name();
3098
+		if ($table_obj instanceof EE_Secondary_Table) {
3099
+			$table_pk_field_name = $table_obj->get_fk_on_table();
3100
+		} else {
3101
+			$table_pk_field_name = $table_obj->get_pk_column();
3102
+		}
3103
+
3104
+		$query  =
3105
+			"UPDATE `{$table_name}`
3106 3106
             SET "
3107
-            . $new_value_sql
3108
-            . $wpdb->prepare(
3109
-                "
3107
+			. $new_value_sql
3108
+			. $wpdb->prepare(
3109
+				"
3110 3110
             WHERE `{$table_pk_field_name}` = %d;",
3111
-                $table_pk_value
3112
-            );
3113
-        $result = $wpdb->query($query);
3114
-        foreach ($fields as $field) {
3115
-            // If it was successful, we'd like to know the new value.
3116
-            // If it failed, we'd also like to know the new value.
3117
-            $new_value = $this->_model->get_var(
3118
-                $this->_model->alter_query_params_to_restrict_by_ID(
3119
-                    $this->_model->get_index_primary_key_string(
3120
-                        $this->model_field_array()
3121
-                    ),
3122
-                    [
3123
-                        'default_where_conditions' => 'minimum',
3124
-                    ]
3125
-                ),
3126
-                $field->get_name()
3127
-            );
3128
-            $this->set_from_db(
3129
-                $field->get_name(),
3130
-                $new_value
3131
-            );
3132
-        }
3133
-        return (bool)$result;
3134
-    }
3135
-
3136
-
3137
-    /**
3138
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3139
-     * Does not allow negative values, however.
3140
-     *
3141
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3142
-     *                                   (positive or negative). One important gotcha: all these values must be
3143
-     *                                   on the same table (eg don't pass in one field for the posts table and
3144
-     *                                   another for the event meta table.)
3145
-     * @return bool
3146
-     * @throws EE_Error
3147
-     * @throws InvalidArgumentException
3148
-     * @throws InvalidDataTypeException
3149
-     * @throws InvalidInterfaceException
3150
-     * @throws ReflectionException
3151
-     * @since 4.9.80.p
3152
-     */
3153
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
3154
-    {
3155
-        global $wpdb;
3156
-        if (empty($fields_n_quantities)) {
3157
-            // No fields to update? Well sure, we updated them to that value just fine.
3158
-            return true;
3159
-        }
3160
-        $fields             = [];
3161
-        $set_sql_statements = [];
3162
-        foreach ($fields_n_quantities as $field_name => $quantity) {
3163
-            $field       = $this->_model->field_settings_for($field_name);
3164
-            $fields[]    = $field;
3165
-            $column_name = $field->get_table_column();
3166
-
3167
-            $abs_qty = absint($quantity);
3168
-            if ($quantity > 0) {
3169
-                // don't let the value be negative as often these fields are unsigned
3170
-                $set_sql_statements[] = $wpdb->prepare(
3171
-                    "`{$column_name}` = `{$column_name}` + %d",
3172
-                    $abs_qty
3173
-                );
3174
-            } else {
3175
-                $set_sql_statements[] = $wpdb->prepare(
3176
-                    "`{$column_name}` = CASE
3111
+				$table_pk_value
3112
+			);
3113
+		$result = $wpdb->query($query);
3114
+		foreach ($fields as $field) {
3115
+			// If it was successful, we'd like to know the new value.
3116
+			// If it failed, we'd also like to know the new value.
3117
+			$new_value = $this->_model->get_var(
3118
+				$this->_model->alter_query_params_to_restrict_by_ID(
3119
+					$this->_model->get_index_primary_key_string(
3120
+						$this->model_field_array()
3121
+					),
3122
+					[
3123
+						'default_where_conditions' => 'minimum',
3124
+					]
3125
+				),
3126
+				$field->get_name()
3127
+			);
3128
+			$this->set_from_db(
3129
+				$field->get_name(),
3130
+				$new_value
3131
+			);
3132
+		}
3133
+		return (bool)$result;
3134
+	}
3135
+
3136
+
3137
+	/**
3138
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3139
+	 * Does not allow negative values, however.
3140
+	 *
3141
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3142
+	 *                                   (positive or negative). One important gotcha: all these values must be
3143
+	 *                                   on the same table (eg don't pass in one field for the posts table and
3144
+	 *                                   another for the event meta table.)
3145
+	 * @return bool
3146
+	 * @throws EE_Error
3147
+	 * @throws InvalidArgumentException
3148
+	 * @throws InvalidDataTypeException
3149
+	 * @throws InvalidInterfaceException
3150
+	 * @throws ReflectionException
3151
+	 * @since 4.9.80.p
3152
+	 */
3153
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
3154
+	{
3155
+		global $wpdb;
3156
+		if (empty($fields_n_quantities)) {
3157
+			// No fields to update? Well sure, we updated them to that value just fine.
3158
+			return true;
3159
+		}
3160
+		$fields             = [];
3161
+		$set_sql_statements = [];
3162
+		foreach ($fields_n_quantities as $field_name => $quantity) {
3163
+			$field       = $this->_model->field_settings_for($field_name);
3164
+			$fields[]    = $field;
3165
+			$column_name = $field->get_table_column();
3166
+
3167
+			$abs_qty = absint($quantity);
3168
+			if ($quantity > 0) {
3169
+				// don't let the value be negative as often these fields are unsigned
3170
+				$set_sql_statements[] = $wpdb->prepare(
3171
+					"`{$column_name}` = `{$column_name}` + %d",
3172
+					$abs_qty
3173
+				);
3174
+			} else {
3175
+				$set_sql_statements[] = $wpdb->prepare(
3176
+					"`{$column_name}` = CASE
3177 3177
                        WHEN (`{$column_name}` >= %d)
3178 3178
                        THEN `{$column_name}` - %d
3179 3179
                        ELSE 0
3180 3180
                     END",
3181
-                    $abs_qty,
3182
-                    $abs_qty
3183
-                );
3184
-            }
3185
-        }
3186
-        return $this->updateFieldsInDB(
3187
-            $fields,
3188
-            implode(', ', $set_sql_statements)
3189
-        );
3190
-    }
3191
-
3192
-
3193
-    /**
3194
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3195
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3196
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3197
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3198
-     * Otherwise returns false.
3199
-     *
3200
-     * @param string $field_name_to_bump
3201
-     * @param string $field_name_affecting_total
3202
-     * @param string $limit_field_name
3203
-     * @param int    $quantity
3204
-     * @return bool
3205
-     * @throws EE_Error
3206
-     * @throws InvalidArgumentException
3207
-     * @throws InvalidDataTypeException
3208
-     * @throws InvalidInterfaceException
3209
-     * @throws ReflectionException
3210
-     * @since 4.9.80.p
3211
-     */
3212
-    public function incrementFieldConditionallyInDb(
3213
-        $field_name_to_bump,
3214
-        $field_name_affecting_total,
3215
-        $limit_field_name,
3216
-        $quantity
3217
-    ) {
3218
-        global $wpdb;
3219
-        $field       = $this->_model->field_settings_for($field_name_to_bump);
3220
-        $column_name = $field->get_table_column();
3221
-
3222
-        $field_affecting_total  = $this->_model->field_settings_for($field_name_affecting_total);
3223
-        $column_affecting_total = $field_affecting_total->get_table_column();
3224
-
3225
-        $limiting_field  = $this->_model->field_settings_for($limit_field_name);
3226
-        $limiting_column = $limiting_field->get_table_column();
3227
-        return $this->updateFieldsInDB(
3228
-            [$field],
3229
-            $wpdb->prepare(
3230
-                "`{$column_name}` =
3181
+					$abs_qty,
3182
+					$abs_qty
3183
+				);
3184
+			}
3185
+		}
3186
+		return $this->updateFieldsInDB(
3187
+			$fields,
3188
+			implode(', ', $set_sql_statements)
3189
+		);
3190
+	}
3191
+
3192
+
3193
+	/**
3194
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3195
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3196
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3197
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3198
+	 * Otherwise returns false.
3199
+	 *
3200
+	 * @param string $field_name_to_bump
3201
+	 * @param string $field_name_affecting_total
3202
+	 * @param string $limit_field_name
3203
+	 * @param int    $quantity
3204
+	 * @return bool
3205
+	 * @throws EE_Error
3206
+	 * @throws InvalidArgumentException
3207
+	 * @throws InvalidDataTypeException
3208
+	 * @throws InvalidInterfaceException
3209
+	 * @throws ReflectionException
3210
+	 * @since 4.9.80.p
3211
+	 */
3212
+	public function incrementFieldConditionallyInDb(
3213
+		$field_name_to_bump,
3214
+		$field_name_affecting_total,
3215
+		$limit_field_name,
3216
+		$quantity
3217
+	) {
3218
+		global $wpdb;
3219
+		$field       = $this->_model->field_settings_for($field_name_to_bump);
3220
+		$column_name = $field->get_table_column();
3221
+
3222
+		$field_affecting_total  = $this->_model->field_settings_for($field_name_affecting_total);
3223
+		$column_affecting_total = $field_affecting_total->get_table_column();
3224
+
3225
+		$limiting_field  = $this->_model->field_settings_for($limit_field_name);
3226
+		$limiting_column = $limiting_field->get_table_column();
3227
+		return $this->updateFieldsInDB(
3228
+			[$field],
3229
+			$wpdb->prepare(
3230
+				"`{$column_name}` =
3231 3231
             CASE
3232 3232
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3233 3233
                THEN `{$column_name}` + %d
3234 3234
                ELSE `{$column_name}`
3235 3235
             END",
3236
-                $quantity,
3237
-                EE_INF_IN_DB,
3238
-                $quantity
3239
-            )
3240
-        );
3241
-    }
3242
-
3243
-
3244
-    /**
3245
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3246
-     * (probably a bad assumption they have made, oh well)
3247
-     *
3248
-     * @return string
3249
-     */
3250
-    public function __toString()
3251
-    {
3252
-        try {
3253
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3254
-        } catch (Exception $e) {
3255
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3256
-            return '';
3257
-        }
3258
-    }
3259
-
3260
-
3261
-    /**
3262
-     * Clear related model objects if they're already in the DB, because otherwise when we
3263
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3264
-     * This means if we have made changes to those related model objects, and want to unserialize
3265
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3266
-     * Instead, those related model objects should be directly serialized and stored.
3267
-     * Eg, the following won't work:
3268
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3269
-     * $att = $reg->attendee();
3270
-     * $att->set( 'ATT_fname', 'Dirk' );
3271
-     * update_option( 'my_option', serialize( $reg ) );
3272
-     * //END REQUEST
3273
-     * //START NEXT REQUEST
3274
-     * $reg = get_option( 'my_option' );
3275
-     * $reg->attendee()->save();
3276
-     * And would need to be replace with:
3277
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3278
-     * $att = $reg->attendee();
3279
-     * $att->set( 'ATT_fname', 'Dirk' );
3280
-     * update_option( 'my_option', serialize( $reg ) );
3281
-     * //END REQUEST
3282
-     * //START NEXT REQUEST
3283
-     * $att = get_option( 'my_option' );
3284
-     * $att->save();
3285
-     *
3286
-     * @return array
3287
-     * @throws ReflectionException
3288
-     * @throws InvalidArgumentException
3289
-     * @throws InvalidInterfaceException
3290
-     * @throws InvalidDataTypeException
3291
-     * @throws EE_Error
3292
-     */
3293
-    public function __sleep()
3294
-    {
3295
-        foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3296
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3297
-                $classname = 'EE_' . $this->_model->get_this_model_name();
3298
-                if ($this->get_one_from_cache($relation_name) instanceof $classname
3299
-                    && $this->get_one_from_cache($relation_name)->ID()
3300
-                ) {
3301
-                    $this->clear_cache(
3302
-                        $relation_name,
3303
-                        $this->get_one_from_cache($relation_name)->ID()
3304
-                    );
3305
-                }
3306
-            }
3307
-        }
3308
-        $this->_props_n_values_provided_in_constructor = [];
3309
-        $properties_to_serialize                       = get_object_vars($this);
3310
-        // don't serialize the model. It's big and that risks recursion
3311
-        unset($properties_to_serialize['_model']);
3312
-        return array_keys($properties_to_serialize);
3313
-    }
3314
-
3315
-
3316
-    /**
3317
-     * restore _props_n_values_provided_in_constructor
3318
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3319
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3320
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3321
-     */
3322
-    public function __wakeup()
3323
-    {
3324
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3325
-    }
3326
-
3327
-
3328
-    /**
3329
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3330
-     * distinct with the clone host instance are also cloned.
3331
-     */
3332
-    public function __clone()
3333
-    {
3334
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3335
-        foreach ($this->_fields as $field => $value) {
3336
-            if ($value instanceof DateTime) {
3337
-                $this->_fields[ $field ] = clone $value;
3338
-            }
3339
-        }
3340
-    }
3236
+				$quantity,
3237
+				EE_INF_IN_DB,
3238
+				$quantity
3239
+			)
3240
+		);
3241
+	}
3242
+
3243
+
3244
+	/**
3245
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3246
+	 * (probably a bad assumption they have made, oh well)
3247
+	 *
3248
+	 * @return string
3249
+	 */
3250
+	public function __toString()
3251
+	{
3252
+		try {
3253
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3254
+		} catch (Exception $e) {
3255
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3256
+			return '';
3257
+		}
3258
+	}
3259
+
3260
+
3261
+	/**
3262
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3263
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3264
+	 * This means if we have made changes to those related model objects, and want to unserialize
3265
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3266
+	 * Instead, those related model objects should be directly serialized and stored.
3267
+	 * Eg, the following won't work:
3268
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3269
+	 * $att = $reg->attendee();
3270
+	 * $att->set( 'ATT_fname', 'Dirk' );
3271
+	 * update_option( 'my_option', serialize( $reg ) );
3272
+	 * //END REQUEST
3273
+	 * //START NEXT REQUEST
3274
+	 * $reg = get_option( 'my_option' );
3275
+	 * $reg->attendee()->save();
3276
+	 * And would need to be replace with:
3277
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3278
+	 * $att = $reg->attendee();
3279
+	 * $att->set( 'ATT_fname', 'Dirk' );
3280
+	 * update_option( 'my_option', serialize( $reg ) );
3281
+	 * //END REQUEST
3282
+	 * //START NEXT REQUEST
3283
+	 * $att = get_option( 'my_option' );
3284
+	 * $att->save();
3285
+	 *
3286
+	 * @return array
3287
+	 * @throws ReflectionException
3288
+	 * @throws InvalidArgumentException
3289
+	 * @throws InvalidInterfaceException
3290
+	 * @throws InvalidDataTypeException
3291
+	 * @throws EE_Error
3292
+	 */
3293
+	public function __sleep()
3294
+	{
3295
+		foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3296
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3297
+				$classname = 'EE_' . $this->_model->get_this_model_name();
3298
+				if ($this->get_one_from_cache($relation_name) instanceof $classname
3299
+					&& $this->get_one_from_cache($relation_name)->ID()
3300
+				) {
3301
+					$this->clear_cache(
3302
+						$relation_name,
3303
+						$this->get_one_from_cache($relation_name)->ID()
3304
+					);
3305
+				}
3306
+			}
3307
+		}
3308
+		$this->_props_n_values_provided_in_constructor = [];
3309
+		$properties_to_serialize                       = get_object_vars($this);
3310
+		// don't serialize the model. It's big and that risks recursion
3311
+		unset($properties_to_serialize['_model']);
3312
+		return array_keys($properties_to_serialize);
3313
+	}
3314
+
3315
+
3316
+	/**
3317
+	 * restore _props_n_values_provided_in_constructor
3318
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3319
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3320
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3321
+	 */
3322
+	public function __wakeup()
3323
+	{
3324
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3325
+	}
3326
+
3327
+
3328
+	/**
3329
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3330
+	 * distinct with the clone host instance are also cloned.
3331
+	 */
3332
+	public function __clone()
3333
+	{
3334
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3335
+		foreach ($this->_fields as $field => $value) {
3336
+			if ($value instanceof DateTime) {
3337
+				$this->_fields[ $field ] = clone $value;
3338
+			}
3339
+		}
3340
+	}
3341 3341
 }
Please login to merge, or discard this patch.
Spacing   +130 added lines, -130 removed lines patch added patch discarded remove patch
@@ -146,7 +146,7 @@  discard block
 block discarded – undo
146 146
         $fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
147 147
         // verify client code has not passed any invalid field names
148 148
         foreach ($fieldValues as $field_name => $field_value) {
149
-            if (! isset($model_fields[ $field_name ])) {
149
+            if ( ! isset($model_fields[$field_name])) {
150 150
                 throw new EE_Error(
151 151
                     sprintf(
152 152
                         esc_html__(
@@ -161,12 +161,12 @@  discard block
 block discarded – undo
161 161
             }
162 162
         }
163 163
         $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
-        if (! empty($date_formats) && is_array($date_formats)) {
164
+        if ( ! empty($date_formats) && is_array($date_formats)) {
165 165
             list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
166 166
         } else {
167 167
             // set default formats for date and time
168
-            $this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
169
-            $this->_tm_frmt = (string)get_option('time_format', 'g:i a');
168
+            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
+            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170 170
         }
171 171
         // if db model is instantiating
172 172
         if ($by_db) {
@@ -174,7 +174,7 @@  discard block
 block discarded – undo
174 174
             foreach ($model_fields as $fieldName => $field) {
175 175
                 $this->set_from_db(
176 176
                     $fieldName,
177
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
177
+                    isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null
178 178
                 );
179 179
             }
180 180
         } else {
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
             foreach ($model_fields as $fieldName => $field) {
184 184
                 $this->set(
185 185
                     $fieldName,
186
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
186
+                    isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null,
187 187
                     true
188 188
                 );
189 189
             }
@@ -191,15 +191,15 @@  discard block
 block discarded – undo
191 191
         // remember what values were passed to this constructor
192 192
         $this->_props_n_values_provided_in_constructor = $fieldValues;
193 193
         // remember in entity mapper
194
-        if (! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
194
+        if ( ! $by_db && $this->_model->has_primary_key_field() && $this->ID()) {
195 195
             $this->_model->add_to_entity_map($this);
196 196
         }
197 197
         // setup all the relations
198 198
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
199 199
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
-                $this->_model_relations[ $relation_name ] = null;
200
+                $this->_model_relations[$relation_name] = null;
201 201
             } else {
202
-                $this->_model_relations[ $relation_name ] = [];
202
+                $this->_model_relations[$relation_name] = [];
203 203
             }
204 204
         }
205 205
         /**
@@ -250,10 +250,10 @@  discard block
 block discarded – undo
250 250
     public function get_original($field_name)
251 251
     {
252 252
         if (
253
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
253
+            isset($this->_props_n_values_provided_in_constructor[$field_name])
254 254
             && $field_settings = $this->_model->field_settings_for($field_name)
255 255
         ) {
256
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
256
+            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
257 257
         }
258 258
         return null;
259 259
     }
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
         // then don't do anything
291 291
         if (
292 292
             ! $use_default
293
-            && $this->_fields[ $field_name ] === $field_value
293
+            && $this->_fields[$field_name] === $field_value
294 294
             && $this->ID()
295 295
         ) {
296 296
             return;
@@ -307,7 +307,7 @@  discard block
 block discarded – undo
307 307
             $holder_of_value = $field_obj->prepare_for_set($field_value);
308 308
             // should the value be null?
309 309
             if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
310
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
310
+                $this->_fields[$field_name] = $field_obj->get_default_value();
311 311
                 /**
312 312
                  * To save having to refactor all the models, if a default value is used for a
313 313
                  * EE_Datetime_Field, and that value is not null nor is it a DateTime
@@ -318,15 +318,15 @@  discard block
 block discarded – undo
318 318
                  */
319 319
                 if (
320 320
                     $field_obj instanceof EE_Datetime_Field
321
-                    && $this->_fields[ $field_name ] !== null
322
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
321
+                    && $this->_fields[$field_name] !== null
322
+                    && ! $this->_fields[$field_name] instanceof DateTime
323 323
                 ) {
324
-                    empty($this->_fields[ $field_name ])
324
+                    empty($this->_fields[$field_name])
325 325
                         ? $this->set($field_name, time())
326
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
326
+                        : $this->set($field_name, $this->_fields[$field_name]);
327 327
                 }
328 328
             } else {
329
-                $this->_fields[ $field_name ] = $holder_of_value;
329
+                $this->_fields[$field_name] = $holder_of_value;
330 330
             }
331 331
             // if we're not in the constructor...
332 332
             // now check if what we set was a primary key
@@ -389,8 +389,8 @@  discard block
 block discarded – undo
389 389
      */
390 390
     public function getCustomSelect($alias)
391 391
     {
392
-        return isset($this->custom_selection_results[ $alias ])
393
-            ? $this->custom_selection_results[ $alias ]
392
+        return isset($this->custom_selection_results[$alias])
393
+            ? $this->custom_selection_results[$alias]
394 394
             : null;
395 395
     }
396 396
 
@@ -475,8 +475,8 @@  discard block
 block discarded – undo
475 475
         foreach ($model_fields as $field_name => $field_obj) {
476 476
             if ($field_obj instanceof EE_Datetime_Field) {
477 477
                 $field_obj->set_timezone($this->_timezone);
478
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
479
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
478
+                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
479
+                    EEH_DTT_Helper::setTimezone($this->_fields[$field_name], new DateTimeZone($this->_timezone));
480 480
                 }
481 481
             }
482 482
         }
@@ -534,7 +534,7 @@  discard block
 block discarded – undo
534 534
      */
535 535
     public function get_format($full = true)
536 536
     {
537
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
537
+        return $full ? $this->_dt_frmt.' '.$this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
538 538
     }
539 539
 
540 540
 
@@ -559,11 +559,11 @@  discard block
 block discarded – undo
559 559
     public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
560 560
     {
561 561
         // its entirely possible that there IS no related object yet in which case there is nothing to cache.
562
-        if (! $object_to_cache instanceof EE_Base_Class) {
562
+        if ( ! $object_to_cache instanceof EE_Base_Class) {
563 563
             return false;
564 564
         }
565 565
         // also get "how" the object is related, or throw an error
566
-        if (! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
566
+        if ( ! $relationship_to_model = $this->_model->related_settings_for($relationName)) {
567 567
             throw new EE_Error(
568 568
                 sprintf(
569 569
                     esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
@@ -577,38 +577,38 @@  discard block
 block discarded – undo
577 577
             // if it's a "belongs to" relationship, then there's only one related model object
578 578
             // eg, if this is a registration, there's only 1 attendee for it
579 579
             // so for these model objects just set it to be cached
580
-            $this->_model_relations[ $relationName ] = $object_to_cache;
580
+            $this->_model_relations[$relationName] = $object_to_cache;
581 581
             $return                                  = true;
582 582
         } else {
583 583
             // otherwise, this is the "many" side of a one to many relationship,
584 584
             // so we'll add the object to the array of related objects for that type.
585 585
             // eg: if this is an event, there are many registrations for that event,
586 586
             // so we cache the registrations in an array
587
-            if (! is_array($this->_model_relations[ $relationName ])) {
587
+            if ( ! is_array($this->_model_relations[$relationName])) {
588 588
                 // if for some reason, the cached item is a model object,
589 589
                 // then stick that in the array, otherwise start with an empty array
590
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
590
+                $this->_model_relations[$relationName] = $this->_model_relations[$relationName]
591 591
                                                            instanceof
592 592
                                                            EE_Base_Class
593
-                    ? [$this->_model_relations[ $relationName ]] : [];
593
+                    ? [$this->_model_relations[$relationName]] : [];
594 594
             }
595 595
             // first check for a cache_id which is normally empty
596
-            if (! empty($cache_id)) {
596
+            if ( ! empty($cache_id)) {
597 597
                 // if the cache_id exists, then it means we are purposely trying to cache this
598 598
                 // with a known key that can then be used to retrieve the object later on
599
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
599
+                $this->_model_relations[$relationName][$cache_id] = $object_to_cache;
600 600
                 $return                                               = $cache_id;
601 601
             } elseif ($object_to_cache->ID()) {
602 602
                 // OR the cached object originally came from the db, so let's just use it's PK for an ID
603
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
603
+                $this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
604 604
                 $return                                                            = $object_to_cache->ID();
605 605
             } else {
606 606
                 // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
607
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
607
+                $this->_model_relations[$relationName][] = $object_to_cache;
608 608
                 // move the internal pointer to the end of the array
609
-                end($this->_model_relations[ $relationName ]);
609
+                end($this->_model_relations[$relationName]);
610 610
                 // and grab the key so that we can return it
611
-                $return = key($this->_model_relations[ $relationName ]);
611
+                $return = key($this->_model_relations[$relationName]);
612 612
             }
613 613
         }
614 614
         return $return;
@@ -633,7 +633,7 @@  discard block
 block discarded – undo
633 633
         // first make sure this property exists
634 634
         $this->_model->field_settings_for($field_name);
635 635
         $cache_type                                             = empty($cache_type) ? 'standard' : $cache_type;
636
-        $this->_cached_properties[ $field_name ][ $cache_type ] = $value;
636
+        $this->_cached_properties[$field_name][$cache_type] = $value;
637 637
     }
638 638
 
639 639
 
@@ -660,9 +660,9 @@  discard block
 block discarded – undo
660 660
         // verify the field exists
661 661
         $this->_model->field_settings_for($field_name);
662 662
         $cache_type = $pretty ? 'pretty' : 'standard';
663
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
664
-        if (isset($this->_cached_properties[ $field_name ][ $cache_type ])) {
665
-            return $this->_cached_properties[ $field_name ][ $cache_type ];
663
+        $cache_type .= ! empty($extra_cache_ref) ? '_'.$extra_cache_ref : '';
664
+        if (isset($this->_cached_properties[$field_name][$cache_type])) {
665
+            return $this->_cached_properties[$field_name][$cache_type];
666 666
         }
667 667
         $value = $this->_get_fresh_property($field_name, $pretty, $extra_cache_ref);
668 668
         $this->_set_cached_property($field_name, $value, $cache_type);
@@ -689,12 +689,12 @@  discard block
 block discarded – undo
689 689
         if ($field_obj instanceof EE_Datetime_Field) {
690 690
             $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
691 691
         }
692
-        if (! isset($this->_fields[ $field_name ])) {
693
-            $this->_fields[ $field_name ] = null;
692
+        if ( ! isset($this->_fields[$field_name])) {
693
+            $this->_fields[$field_name] = null;
694 694
         }
695 695
         return $pretty
696
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $field_name ], $extra_cache_ref)
697
-            : $field_obj->prepare_for_get($this->_fields[ $field_name ]);
696
+            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$field_name], $extra_cache_ref)
697
+            : $field_obj->prepare_for_get($this->_fields[$field_name]);
698 698
     }
699 699
 
700 700
 
@@ -750,8 +750,8 @@  discard block
 block discarded – undo
750 750
      */
751 751
     protected function _clear_cached_property($property_name)
752 752
     {
753
-        if (isset($this->_cached_properties[ $property_name ])) {
754
-            unset($this->_cached_properties[ $property_name ]);
753
+        if (isset($this->_cached_properties[$property_name])) {
754
+            unset($this->_cached_properties[$property_name]);
755 755
         }
756 756
     }
757 757
 
@@ -803,7 +803,7 @@  discard block
 block discarded – undo
803 803
     {
804 804
         $relationship_to_model = $this->_model->related_settings_for($relationName);
805 805
         $index_in_cache        = '';
806
-        if (! $relationship_to_model) {
806
+        if ( ! $relationship_to_model) {
807 807
             throw new EE_Error(
808 808
                 sprintf(
809 809
                     esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
@@ -814,10 +814,10 @@  discard block
 block discarded – undo
814 814
         }
815 815
         if ($clear_all) {
816 816
             $obj_removed                             = true;
817
-            $this->_model_relations[ $relationName ] = null;
817
+            $this->_model_relations[$relationName] = null;
818 818
         } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
819
-            $obj_removed                             = $this->_model_relations[ $relationName ];
820
-            $this->_model_relations[ $relationName ] = null;
819
+            $obj_removed                             = $this->_model_relations[$relationName];
820
+            $this->_model_relations[$relationName] = null;
821 821
         } else {
822 822
             if (
823 823
                 $object_to_remove_or_index_into_array instanceof EE_Base_Class
@@ -825,12 +825,12 @@  discard block
 block discarded – undo
825 825
             ) {
826 826
                 $index_in_cache = $object_to_remove_or_index_into_array->ID();
827 827
                 if (
828
-                    is_array($this->_model_relations[ $relationName ])
829
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
828
+                    is_array($this->_model_relations[$relationName])
829
+                    && ! isset($this->_model_relations[$relationName][$index_in_cache])
830 830
                 ) {
831 831
                     $index_found_at = null;
832 832
                     // find this object in the array even though it has a different key
833
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
833
+                    foreach ($this->_model_relations[$relationName] as $index => $obj) {
834 834
                         /** @noinspection TypeUnsafeComparisonInspection */
835 835
                         if (
836 836
                             $obj instanceof EE_Base_Class
@@ -864,9 +864,9 @@  discard block
 block discarded – undo
864 864
             }
865 865
             // supposedly we've found it. But it could just be that the client code
866 866
             // provided a bad index/object
867
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
868
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
869
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
867
+            if (isset($this->_model_relations[$relationName][$index_in_cache])) {
868
+                $obj_removed = $this->_model_relations[$relationName][$index_in_cache];
869
+                unset($this->_model_relations[$relationName][$index_in_cache]);
870 870
             } else {
871 871
                 // that thing was never cached anyways.
872 872
                 $obj_removed = null;
@@ -896,7 +896,7 @@  discard block
 block discarded – undo
896 896
         $current_cache_id = ''
897 897
     ) {
898 898
         // verify that incoming object is of the correct type
899
-        $obj_class = 'EE_' . $relationName;
899
+        $obj_class = 'EE_'.$relationName;
900 900
         if ($newly_saved_object instanceof $obj_class) {
901 901
             /* @type EE_Base_Class $newly_saved_object */
902 902
             // now get the type of relation
@@ -904,18 +904,18 @@  discard block
 block discarded – undo
904 904
             // if this is a 1:1 relationship
905 905
             if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
906 906
                 // then just replace the cached object with the newly saved object
907
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
907
+                $this->_model_relations[$relationName] = $newly_saved_object;
908 908
                 return true;
909 909
                 // or if it's some kind of sordid feral polyamorous relationship...
910 910
             }
911 911
             if (
912
-                is_array($this->_model_relations[ $relationName ])
913
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
912
+                is_array($this->_model_relations[$relationName])
913
+                && isset($this->_model_relations[$relationName][$current_cache_id])
914 914
             ) {
915 915
                 // then remove the current cached item
916
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
916
+                unset($this->_model_relations[$relationName][$current_cache_id]);
917 917
                 // and cache the newly saved object using it's new ID
918
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
918
+                $this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
919 919
                 return true;
920 920
             }
921 921
         }
@@ -932,8 +932,8 @@  discard block
 block discarded – undo
932 932
      */
933 933
     public function get_one_from_cache($relationName)
934 934
     {
935
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
936
-            ? $this->_model_relations[ $relationName ]
935
+        $cached_array_or_object = isset($this->_model_relations[$relationName])
936
+            ? $this->_model_relations[$relationName]
937 937
             : null;
938 938
         if (is_array($cached_array_or_object)) {
939 939
             return array_shift($cached_array_or_object);
@@ -956,7 +956,7 @@  discard block
 block discarded – undo
956 956
      */
957 957
     public function get_all_from_cache($relationName)
958 958
     {
959
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : [];
959
+        $objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : [];
960 960
         // if the result is not an array, but exists, make it an array
961 961
         $objects = is_array($objects) ? $objects : [$objects];
962 962
         // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
@@ -1135,7 +1135,7 @@  discard block
 block discarded – undo
1135 1135
             } else {
1136 1136
                 $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1137 1137
             }
1138
-            $this->_fields[ $field_name ] = $field_value;
1138
+            $this->_fields[$field_name] = $field_value;
1139 1139
             $this->_clear_cached_property($field_name);
1140 1140
         }
1141 1141
     }
@@ -1173,9 +1173,9 @@  discard block
 block discarded – undo
1173 1173
     public function get_raw($field_name)
1174 1174
     {
1175 1175
         $field_settings = $this->_model->field_settings_for($field_name);
1176
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1177
-            ? $this->_fields[ $field_name ]->format('U')
1178
-            : $this->_fields[ $field_name ];
1176
+        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
1177
+            ? $this->_fields[$field_name]->format('U')
1178
+            : $this->_fields[$field_name];
1179 1179
     }
1180 1180
 
1181 1181
 
@@ -1196,7 +1196,7 @@  discard block
 block discarded – undo
1196 1196
     public function get_DateTime_object($field_name)
1197 1197
     {
1198 1198
         $field_settings = $this->_model->field_settings_for($field_name);
1199
-        if (! $field_settings instanceof EE_Datetime_Field) {
1199
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1200 1200
             EE_Error::add_error(
1201 1201
                 sprintf(
1202 1202
                     esc_html__(
@@ -1211,8 +1211,8 @@  discard block
 block discarded – undo
1211 1211
             );
1212 1212
             return false;
1213 1213
         }
1214
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1215
-            ? clone $this->_fields[ $field_name ]
1214
+        return isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime
1215
+            ? clone $this->_fields[$field_name]
1216 1216
             : null;
1217 1217
     }
1218 1218
 
@@ -1266,7 +1266,7 @@  discard block
 block discarded – undo
1266 1266
      */
1267 1267
     public function get_f($field_name)
1268 1268
     {
1269
-        return (string)$this->get_pretty($field_name, 'form_input');
1269
+        return (string) $this->get_pretty($field_name, 'form_input');
1270 1270
     }
1271 1271
 
1272 1272
 
@@ -1449,7 +1449,7 @@  discard block
 block discarded – undo
1449 1449
      */
1450 1450
     public function get_i18n_datetime($field_name, $format = '')
1451 1451
     {
1452
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1452
+        $format = empty($format) ? $this->_dt_frmt.' '.$this->_tm_frmt : $format;
1453 1453
         return date_i18n(
1454 1454
             $format,
1455 1455
             EEH_DTT_Helper::get_timestamp_with_offset(
@@ -1560,21 +1560,21 @@  discard block
 block discarded – undo
1560 1560
         $what = in_array($what, ['T', 'D', 'B']) ? $what : 'T';
1561 1561
         switch ($what) {
1562 1562
             case 'T':
1563
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
1563
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_time(
1564 1564
                     $datetime_value,
1565
-                    $this->_fields[ $field_name ]
1565
+                    $this->_fields[$field_name]
1566 1566
                 );
1567 1567
                 $this->_has_changes           = true;
1568 1568
                 break;
1569 1569
             case 'D':
1570
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
1570
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_date(
1571 1571
                     $datetime_value,
1572
-                    $this->_fields[ $field_name ]
1572
+                    $this->_fields[$field_name]
1573 1573
                 );
1574 1574
                 $this->_has_changes           = true;
1575 1575
                 break;
1576 1576
             case 'B':
1577
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
1577
+                $this->_fields[$field_name] = $field->prepare_for_set($datetime_value);
1578 1578
                 $this->_has_changes           = true;
1579 1579
                 break;
1580 1580
         }
@@ -1614,9 +1614,9 @@  discard block
 block discarded – undo
1614 1614
         }
1615 1615
         $original_timezone = $this->_timezone;
1616 1616
         $this->set_timezone($timezone);
1617
-        $fn   = (array)$field_name;
1618
-        $args = array_merge($fn, (array)$args);
1619
-        if (! method_exists($this, $callback)) {
1617
+        $fn   = (array) $field_name;
1618
+        $args = array_merge($fn, (array) $args);
1619
+        if ( ! method_exists($this, $callback)) {
1620 1620
             throw new EE_Error(
1621 1621
                 sprintf(
1622 1622
                     esc_html__(
@@ -1627,8 +1627,8 @@  discard block
 block discarded – undo
1627 1627
                 )
1628 1628
             );
1629 1629
         }
1630
-        $args   = (array)$args;
1631
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
1630
+        $args   = (array) $args;
1631
+        $return = $prepend.call_user_func_array([$this, $callback], $args).$append;
1632 1632
         $this->set_timezone($original_timezone);
1633 1633
         return $return;
1634 1634
     }
@@ -1741,8 +1741,8 @@  discard block
 block discarded – undo
1741 1741
     public function refresh_cache_of_related_objects()
1742 1742
     {
1743 1743
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
1744
-            if (! empty($this->_model_relations[ $relation_name ])) {
1745
-                $related_objects = $this->_model_relations[ $relation_name ];
1744
+            if ( ! empty($this->_model_relations[$relation_name])) {
1745
+                $related_objects = $this->_model_relations[$relation_name];
1746 1746
                 if ($relation_obj instanceof EE_Belongs_To_Relation) {
1747 1747
                     // this relation only stores a single model object, not an array
1748 1748
                     // but let's make it consistent
@@ -1788,7 +1788,7 @@  discard block
 block discarded – undo
1788 1788
          * @param array         $set_cols_n_values
1789 1789
          * @param EE_Base_Class $model_object
1790 1790
          */
1791
-        $set_cols_n_values = (array)apply_filters(
1791
+        $set_cols_n_values = (array) apply_filters(
1792 1792
             'FHEE__EE_Base_Class__save__set_cols_n_values',
1793 1793
             $set_cols_n_values,
1794 1794
             $this
@@ -1798,7 +1798,7 @@  discard block
 block discarded – undo
1798 1798
             $this->set($column, $value);
1799 1799
         }
1800 1800
         // no changes ? then don't do anything
1801
-        if (! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
1801
+        if ( ! $this->_has_changes && $this->ID() && $this->_model->get_primary_key_field()->is_auto_increment()) {
1802 1802
             return 0;
1803 1803
         }
1804 1804
         /**
@@ -1808,7 +1808,7 @@  discard block
 block discarded – undo
1808 1808
          * @param EE_Base_Class $model_object the model object about to be saved.
1809 1809
          */
1810 1810
         do_action('AHEE__EE_Base_Class__save__begin', $this);
1811
-        if (! $this->allow_persist()) {
1811
+        if ( ! $this->allow_persist()) {
1812 1812
             return 0;
1813 1813
         }
1814 1814
         // now get current attribute values
@@ -1823,10 +1823,10 @@  discard block
 block discarded – undo
1823 1823
         if ($this->_model->has_primary_key_field()) {
1824 1824
             if ($this->_model->get_primary_key_field()->is_auto_increment()) {
1825 1825
                 // ok check if it's set, if so: update; if not, insert
1826
-                if (! empty($save_cols_n_values[ $this->_model->primary_key_name() ])) {
1826
+                if ( ! empty($save_cols_n_values[$this->_model->primary_key_name()])) {
1827 1827
                     $results = $this->_model->update_by_ID($save_cols_n_values, $this->ID());
1828 1828
                 } else {
1829
-                    unset($save_cols_n_values[ $this->_model->primary_key_name() ]);
1829
+                    unset($save_cols_n_values[$this->_model->primary_key_name()]);
1830 1830
                     $results = $this->_model->insert($save_cols_n_values);
1831 1831
                     if ($results) {
1832 1832
                         // if successful, set the primary key
@@ -1836,7 +1836,7 @@  discard block
 block discarded – undo
1836 1836
                         // will get added to the mapper before we can add this one!
1837 1837
                         // but if we just avoid using the SET method, all that headache can be avoided
1838 1838
                         $pk_field_name                   = $this->_model->primary_key_name();
1839
-                        $this->_fields[ $pk_field_name ] = $results;
1839
+                        $this->_fields[$pk_field_name] = $results;
1840 1840
                         $this->_clear_cached_property($pk_field_name);
1841 1841
                         $this->_model->add_to_entity_map($this);
1842 1842
                         $this->_update_cached_related_model_objs_fks();
@@ -1853,8 +1853,8 @@  discard block
 block discarded – undo
1853 1853
                                     'event_espresso'
1854 1854
                                 ),
1855 1855
                                 get_class($this),
1856
-                                get_class($this->_model) . '::instance()->add_to_entity_map()',
1857
-                                get_class($this->_model) . '::instance()->get_one_by_ID()',
1856
+                                get_class($this->_model).'::instance()->add_to_entity_map()',
1857
+                                get_class($this->_model).'::instance()->get_one_by_ID()',
1858 1858
                                 '<br />'
1859 1859
                             )
1860 1860
                         );
@@ -1878,7 +1878,7 @@  discard block
 block discarded – undo
1878 1878
                     $save_cols_n_values,
1879 1879
                     $this->_model->get_combined_primary_key_fields()
1880 1880
                 );
1881
-                $results                     = $this->_model->update(
1881
+                $results = $this->_model->update(
1882 1882
                     $save_cols_n_values,
1883 1883
                     $combined_pk_fields_n_values
1884 1884
                 );
@@ -1955,27 +1955,27 @@  discard block
 block discarded – undo
1955 1955
     public function save_new_cached_related_model_objs()
1956 1956
     {
1957 1957
         // make sure this has been saved
1958
-        if (! $this->ID()) {
1958
+        if ( ! $this->ID()) {
1959 1959
             $id = $this->save();
1960 1960
         } else {
1961 1961
             $id = $this->ID();
1962 1962
         }
1963 1963
         // now save all the NEW cached model objects  (ie they don't exist in the DB)
1964 1964
         foreach ($this->_model->relation_settings() as $relationName => $relationObj) {
1965
-            if ($this->_model_relations[ $relationName ]) {
1965
+            if ($this->_model_relations[$relationName]) {
1966 1966
                 // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1967 1967
                 // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1968 1968
                 /* @var $related_model_obj EE_Base_Class */
1969 1969
                 if ($relationObj instanceof EE_Belongs_To_Relation) {
1970 1970
                     // add a relation to that relation type (which saves the appropriate thing in the process)
1971 1971
                     // but ONLY if it DOES NOT exist in the DB
1972
-                    $related_model_obj = $this->_model_relations[ $relationName ];
1972
+                    $related_model_obj = $this->_model_relations[$relationName];
1973 1973
                     // if( ! $related_model_obj->ID()){
1974 1974
                     $this->_add_relation_to($related_model_obj, $relationName);
1975 1975
                     $related_model_obj->save_new_cached_related_model_objs();
1976 1976
                     // }
1977 1977
                 } else {
1978
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
1978
+                    foreach ($this->_model_relations[$relationName] as $related_model_obj) {
1979 1979
                         // add a relation to that relation type (which saves the appropriate thing in the process)
1980 1980
                         // but ONLY if it DOES NOT exist in the DB
1981 1981
                         // if( ! $related_model_obj->ID()){
@@ -2002,7 +2002,7 @@  discard block
 block discarded – undo
2002 2002
      */
2003 2003
     public function get_model()
2004 2004
     {
2005
-        if (! $this->_model) {
2005
+        if ( ! $this->_model) {
2006 2006
             $modelName    = self::_get_model_classname(get_class($this));
2007 2007
             $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2008 2008
         }
@@ -2026,9 +2026,9 @@  discard block
 block discarded – undo
2026 2026
         $primary_id_ref = self::_get_primary_key_name($classname);
2027 2027
         if (
2028 2028
             array_key_exists($primary_id_ref, $props_n_values)
2029
-            && ! empty($props_n_values[ $primary_id_ref ])
2029
+            && ! empty($props_n_values[$primary_id_ref])
2030 2030
         ) {
2031
-            $id = $props_n_values[ $primary_id_ref ];
2031
+            $id = $props_n_values[$primary_id_ref];
2032 2032
             return self::_get_model($classname)->get_from_entity_map($id);
2033 2033
         }
2034 2034
         return false;
@@ -2063,10 +2063,10 @@  discard block
 block discarded – undo
2063 2063
             $primary_id_ref = self::_get_primary_key_name($classname);
2064 2064
             if (
2065 2065
                 array_key_exists($primary_id_ref, $props_n_values)
2066
-                && ! empty($props_n_values[ $primary_id_ref ])
2066
+                && ! empty($props_n_values[$primary_id_ref])
2067 2067
             ) {
2068 2068
                 $existing = $model->get_one_by_ID(
2069
-                    $props_n_values[ $primary_id_ref ]
2069
+                    $props_n_values[$primary_id_ref]
2070 2070
                 );
2071 2071
             }
2072 2072
         } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
@@ -2078,7 +2078,7 @@  discard block
 block discarded – undo
2078 2078
         }
2079 2079
         if ($existing) {
2080 2080
             // set date formats if present before setting values
2081
-            if (! empty($date_formats) && is_array($date_formats)) {
2081
+            if ( ! empty($date_formats) && is_array($date_formats)) {
2082 2082
                 $existing->set_date_format($date_formats[0]);
2083 2083
                 $existing->set_time_format($date_formats[1]);
2084 2084
             } else {
@@ -2111,7 +2111,7 @@  discard block
 block discarded – undo
2111 2111
     protected static function _get_model($classname, $timezone = null)
2112 2112
     {
2113 2113
         // find model for this class
2114
-        if (! $classname) {
2114
+        if ( ! $classname) {
2115 2115
             throw new EE_Error(
2116 2116
                 sprintf(
2117 2117
                     esc_html__(
@@ -2160,7 +2160,7 @@  discard block
 block discarded – undo
2160 2160
         if (strpos($model_name, 'EE_') === 0) {
2161 2161
             $model_classname = str_replace('EE_', 'EEM_', $model_name);
2162 2162
         } else {
2163
-            $model_classname = 'EEM_' . $model_name;
2163
+            $model_classname = 'EEM_'.$model_name;
2164 2164
         }
2165 2165
         return $model_classname;
2166 2166
     }
@@ -2179,7 +2179,7 @@  discard block
 block discarded – undo
2179 2179
      */
2180 2180
     protected static function _get_primary_key_name($classname = null)
2181 2181
     {
2182
-        if (! $classname) {
2182
+        if ( ! $classname) {
2183 2183
             throw new EE_Error(
2184 2184
                 sprintf(
2185 2185
                     esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
@@ -2207,7 +2207,7 @@  discard block
 block discarded – undo
2207 2207
     {
2208 2208
         // now that we know the name of the variable, use a variable variable to get its value and return its
2209 2209
         if ($this->_model->has_primary_key_field()) {
2210
-            return $this->_fields[ $this->_model->primary_key_name() ];
2210
+            return $this->_fields[$this->_model->primary_key_name()];
2211 2211
         }
2212 2212
         return $this->_model->get_index_primary_key_string($this->_fields);
2213 2213
     }
@@ -2259,7 +2259,7 @@  discard block
 block discarded – undo
2259 2259
             }
2260 2260
         } else {
2261 2261
             // this thing doesn't exist in the DB,  so just cache it
2262
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2262
+            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2263 2263
                 throw new EE_Error(
2264 2264
                     sprintf(
2265 2265
                         esc_html__(
@@ -2426,7 +2426,7 @@  discard block
 block discarded – undo
2426 2426
             } else {
2427 2427
                 // did we already cache the result of this query?
2428 2428
                 $cached_results = $this->get_all_from_cache($relationName);
2429
-                if (! $cached_results) {
2429
+                if ( ! $cached_results) {
2430 2430
                     $related_model_objects = $this->_model->get_all_related(
2431 2431
                         $this,
2432 2432
                         $relationName,
@@ -2538,7 +2538,7 @@  discard block
 block discarded – undo
2538 2538
             } else {
2539 2539
                 // first, check if we've already cached the result of this query
2540 2540
                 $cached_result = $this->get_one_from_cache($relationName);
2541
-                if (! $cached_result) {
2541
+                if ( ! $cached_result) {
2542 2542
                     $related_model_object = $this->_model->get_first_related(
2543 2543
                         $this,
2544 2544
                         $relationName,
@@ -2562,7 +2562,7 @@  discard block
 block discarded – undo
2562 2562
             }
2563 2563
             // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2564 2564
             // just get what's cached on this object
2565
-            if (! $related_model_object) {
2565
+            if ( ! $related_model_object) {
2566 2566
                 $related_model_object = $this->get_one_from_cache($relationName);
2567 2567
             }
2568 2568
         }
@@ -2646,7 +2646,7 @@  discard block
 block discarded – undo
2646 2646
      */
2647 2647
     public function is_set($field_name)
2648 2648
     {
2649
-        return isset($this->_fields[ $field_name ]);
2649
+        return isset($this->_fields[$field_name]);
2650 2650
     }
2651 2651
 
2652 2652
 
@@ -2660,9 +2660,9 @@  discard block
 block discarded – undo
2660 2660
      */
2661 2661
     protected function _property_exists($properties)
2662 2662
     {
2663
-        foreach ((array)$properties as $property_name) {
2663
+        foreach ((array) $properties as $property_name) {
2664 2664
             // first make sure this property exists
2665
-            if (! $this->_fields[ $property_name ]) {
2665
+            if ( ! $this->_fields[$property_name]) {
2666 2666
                 throw new EE_Error(
2667 2667
                     sprintf(
2668 2668
                         esc_html__(
@@ -2693,7 +2693,7 @@  discard block
 block discarded – undo
2693 2693
         $properties = [];
2694 2694
         // remove prepended underscore
2695 2695
         foreach ($fields as $field_name => $settings) {
2696
-            $properties[ $field_name ] = $this->get($field_name);
2696
+            $properties[$field_name] = $this->get($field_name);
2697 2697
         }
2698 2698
         return $properties;
2699 2699
     }
@@ -2730,7 +2730,7 @@  discard block
 block discarded – undo
2730 2730
     {
2731 2731
         $className = get_class($this);
2732 2732
         $tagName   = "FHEE__{$className}__{$methodName}";
2733
-        if (! has_filter($tagName)) {
2733
+        if ( ! has_filter($tagName)) {
2734 2734
             throw new EE_Error(
2735 2735
                 sprintf(
2736 2736
                     esc_html__(
@@ -2775,7 +2775,7 @@  discard block
 block discarded – undo
2775 2775
             $query_params[0]['EXM_value'] = $meta_value;
2776 2776
         }
2777 2777
         $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2778
-        if (! $existing_rows_like_that) {
2778
+        if ( ! $existing_rows_like_that) {
2779 2779
             return $this->add_extra_meta($meta_key, $meta_value);
2780 2780
         }
2781 2781
         foreach ($existing_rows_like_that as $existing_row) {
@@ -2893,7 +2893,7 @@  discard block
 block discarded – undo
2893 2893
                 $values = [];
2894 2894
                 foreach ($results as $result) {
2895 2895
                     if ($result instanceof EE_Extra_Meta) {
2896
-                        $values[ $result->ID() ] = $result->value();
2896
+                        $values[$result->ID()] = $result->value();
2897 2897
                     }
2898 2898
                 }
2899 2899
                 return $values;
@@ -2938,17 +2938,17 @@  discard block
 block discarded – undo
2938 2938
             );
2939 2939
             foreach ($extra_meta_objs as $extra_meta_obj) {
2940 2940
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2941
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2941
+                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2942 2942
                 }
2943 2943
             }
2944 2944
         } else {
2945 2945
             $extra_meta_objs = $this->get_many_related('Extra_Meta');
2946 2946
             foreach ($extra_meta_objs as $extra_meta_obj) {
2947 2947
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2948
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2949
-                        $return_array[ $extra_meta_obj->key() ] = [];
2948
+                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2949
+                        $return_array[$extra_meta_obj->key()] = [];
2950 2950
                     }
2951
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2951
+                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2952 2952
                 }
2953 2953
             }
2954 2954
         }
@@ -3027,8 +3027,8 @@  discard block
 block discarded – undo
3027 3027
                             'event_espresso'
3028 3028
                         ),
3029 3029
                         $this->ID(),
3030
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3031
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3030
+                        get_class($this->get_model()).'::instance()->add_to_entity_map()',
3031
+                        get_class($this->get_model()).'::instance()->refresh_entity_map()'
3032 3032
                     )
3033 3033
                 );
3034 3034
             }
@@ -3061,7 +3061,7 @@  discard block
 block discarded – undo
3061 3061
     {
3062 3062
         // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3063 3063
         // if it wasn't even there to start off.
3064
-        if (! $this->ID()) {
3064
+        if ( ! $this->ID()) {
3065 3065
             $this->save();
3066 3066
         }
3067 3067
         global $wpdb;
@@ -3101,7 +3101,7 @@  discard block
 block discarded – undo
3101 3101
             $table_pk_field_name = $table_obj->get_pk_column();
3102 3102
         }
3103 3103
 
3104
-        $query  =
3104
+        $query =
3105 3105
             "UPDATE `{$table_name}`
3106 3106
             SET "
3107 3107
             . $new_value_sql
@@ -3130,7 +3130,7 @@  discard block
 block discarded – undo
3130 3130
                 $new_value
3131 3131
             );
3132 3132
         }
3133
-        return (bool)$result;
3133
+        return (bool) $result;
3134 3134
     }
3135 3135
 
3136 3136
 
@@ -3294,7 +3294,7 @@  discard block
 block discarded – undo
3294 3294
     {
3295 3295
         foreach ($this->_model->relation_settings() as $relation_name => $relation_obj) {
3296 3296
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
3297
-                $classname = 'EE_' . $this->_model->get_this_model_name();
3297
+                $classname = 'EE_'.$this->_model->get_this_model_name();
3298 3298
                 if ($this->get_one_from_cache($relation_name) instanceof $classname
3299 3299
                     && $this->get_one_from_cache($relation_name)->ID()
3300 3300
                 ) {
@@ -3334,7 +3334,7 @@  discard block
 block discarded – undo
3334 3334
         // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3335 3335
         foreach ($this->_fields as $field => $value) {
3336 3336
             if ($value instanceof DateTime) {
3337
-                $this->_fields[ $field ] = clone $value;
3337
+                $this->_fields[$field] = clone $value;
3338 3338
             }
3339 3339
         }
3340 3340
     }
Please login to merge, or discard this patch.
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   +6545 added lines, -6545 removed lines patch added patch discarded remove patch
@@ -37,6551 +37,6551 @@
 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 null | string $timezone valid PHP DateTimeZone timezone string
1423
-     */
1424
-    public function set_timezone($timezone)
1425
-    {
1426
-        if ($timezone !== null) {
1427
-            $this->_timezone = $timezone;
1428
-        }
1429
-        // note we need to loop through relations and set the timezone on those objects as well.
1430
-        foreach ($this->_model_relations as $relation) {
1431
-            $relation->set_timezone($timezone);
1432
-        }
1433
-        // and finally we do the same for any datetime fields
1434
-        foreach ($this->_fields as $field) {
1435
-            if ($field instanceof EE_Datetime_Field) {
1436
-                $field->set_timezone($timezone);
1437
-            }
1438
-        }
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * This just returns whatever is set for the current timezone.
1444
-     *
1445
-     * @access public
1446
-     * @return string
1447
-     */
1448
-    public function get_timezone()
1449
-    {
1450
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1451
-        if (empty($this->_timezone)) {
1452
-            foreach ($this->_fields as $field) {
1453
-                if ($field instanceof EE_Datetime_Field) {
1454
-                    $this->set_timezone($field->get_timezone());
1455
-                    break;
1456
-                }
1457
-            }
1458
-        }
1459
-        // if timezone STILL empty then return the default timezone for the site.
1460
-        if (empty($this->_timezone)) {
1461
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1462
-        }
1463
-        return $this->_timezone;
1464
-    }
1465
-
1466
-
1467
-    /**
1468
-     * This returns the date formats set for the given field name and also ensures that
1469
-     * $this->_timezone property is set correctly.
1470
-     *
1471
-     * @param string $field_name The name of the field the formats are being retrieved for.
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.
1474
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1475
-     * @since 4.6.x
1476
-     */
1477
-    public function get_formats_for($field_name, $pretty = false)
1478
-    {
1479
-        $field_settings = $this->field_settings_for($field_name);
1480
-        // if not a valid EE_Datetime_Field then throw error
1481
-        if (! $field_settings instanceof EE_Datetime_Field) {
1482
-            throw new EE_Error(
1483
-                sprintf(
1484
-                    esc_html__(
1485
-                        '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.',
1486
-                        'event_espresso'
1487
-                    ),
1488
-                    $field_name
1489
-                )
1490
-            );
1491
-        }
1492
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1493
-        // the field.
1494
-        $this->_timezone = $field_settings->get_timezone();
1495
-        return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1496
-    }
1497
-
1498
-
1499
-    /**
1500
-     * This returns the current time in a format setup for a query on this model.
1501
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1502
-     * it will return:
1503
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1504
-     *  NOW
1505
-     *  - or a unix timestamp (equivalent to time())
1506
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1507
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1508
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1509
-     *
1510
-     * @param string $field_name       The field the current time is needed for.
1511
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1512
-     *                                 formatted string matching the set format for the field in the set timezone will
1513
-     *                                 be returned.
1514
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1515
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1516
-     *                                 exception is triggered.
1517
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1518
-     * @throws Exception
1519
-     * @since 4.6.x
1520
-     */
1521
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1522
-    {
1523
-        $formats  = $this->get_formats_for($field_name);
1524
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1525
-        if ($timestamp) {
1526
-            return $DateTime->format('U');
1527
-        }
1528
-        // not returning timestamp, so return formatted string in timezone.
1529
-        switch ($what) {
1530
-            case 'time':
1531
-                return $DateTime->format($formats[1]);
1532
-            case 'date':
1533
-                return $DateTime->format($formats[0]);
1534
-            default:
1535
-                return $DateTime->format(implode(' ', $formats));
1536
-        }
1537
-    }
1538
-
1539
-
1540
-    /**
1541
-     * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1542
-     * for the model are.  Returns a DateTime object.
1543
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1544
-     * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1545
-     * ignored.
1546
-     *
1547
-     * @param string $field_name      The field being setup.
1548
-     * @param string $timestring      The date time string being used.
1549
-     * @param string $incoming_format The format for the time string.
1550
-     * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1551
-     *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1552
-     *                                format is
1553
-     *                                'U', this is ignored.
1554
-     * @return DateTime
1555
-     * @throws EE_Error
1556
-     */
1557
-    public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1558
-    {
1559
-        // just using this to ensure the timezone is set correctly internally
1560
-        $this->get_formats_for($field_name);
1561
-        // load EEH_DTT_Helper
1562
-        $set_timezone     = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1563
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1564
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1565
-        return DbSafeDateTime::createFromDateTime($incomingDateTime);
1566
-    }
1567
-
1568
-
1569
-    /**
1570
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1571
-     *
1572
-     * @return EE_Table_Base[]
1573
-     */
1574
-    public function get_tables()
1575
-    {
1576
-        return $this->_tables;
1577
-    }
1578
-
1579
-
1580
-    /**
1581
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1582
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1583
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1584
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1585
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1586
-     * model object with EVT_ID = 1
1587
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1588
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1589
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1590
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1591
-     * are not specified)
1592
-     *
1593
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1594
-     *                                         columns!), values are strings, integers, floats, and maybe arrays if
1595
-     *                                         they
1596
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1597
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1598
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1599
-     *                                         ID=34, we'd use this method as follows:
1600
-     *                                         EEM_Transaction::instance()->update(
1601
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1602
-     *                                         array(array('TXN_ID'=>34)));
1603
-     * @param array   $query_params            Eg, consider updating Question's QST_admin_label field is of type
1604
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1605
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1606
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1607
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1608
-     *                                         TRUE, it is assumed that you've already called
1609
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1610
-     *                                         malicious javascript. However, if
1611
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1612
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1613
-     *                                         and every other field, before insertion. We provide this parameter
1614
-     *                                         because model objects perform their prepare_for_set function on all
1615
-     *                                         their values, and so don't need to be called again (and in many cases,
1616
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1617
-     *                                         prepare_for_set method...)
1618
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1619
-     *                                         in this model's entity map according to $fields_n_values that match
1620
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1621
-     *                                         by setting this to FALSE, but be aware that model objects being used
1622
-     *                                         could get out-of-sync with the database
1623
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1624
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1625
-     *                                         bad)
1626
-     * @throws EE_Error
1627
-     * @throws ReflectionException
1628
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1629
-     */
1630
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1631
-    {
1632
-        if (! is_array($query_params)) {
1633
-            EE_Error::doing_it_wrong(
1634
-                'EEM_Base::update',
1635
-                sprintf(
1636
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1637
-                    gettype($query_params)
1638
-                ),
1639
-                '4.6.0'
1640
-            );
1641
-            $query_params = [];
1642
-        }
1643
-        /**
1644
-         * Action called before a model update call has been made.
1645
-         *
1646
-         * @param EEM_Base $model
1647
-         * @param array    $fields_n_values the updated fields and their new values
1648
-         * @param array    $query_params
1649
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1650
-         */
1651
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1652
-        /**
1653
-         * Filters the fields about to be updated given the query parameters. You can provide the
1654
-         * $query_params to $this->get_all() to find exactly which records will be updated
1655
-         *
1656
-         * @param array    $fields_n_values fields and their new values
1657
-         * @param EEM_Base $model           the model being queried
1658
-         * @param array    $query_params
1659
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1660
-         */
1661
-        $fields_n_values = (array)apply_filters(
1662
-            'FHEE__EEM_Base__update__fields_n_values',
1663
-            $fields_n_values,
1664
-            $this,
1665
-            $query_params
1666
-        );
1667
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1668
-        // to do that, for each table, verify that it's PK isn't null.
1669
-        $tables = $this->get_tables();
1670
-        // 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
1671
-        // NOTE: we should make this code more efficient by NOT querying twice
1672
-        // before the real update, but that needs to first go through ALPHA testing
1673
-        // as it's dangerous. says Mike August 8 2014
1674
-        // we want to make sure the default_where strategy is ignored
1675
-        $this->_ignore_where_strategy = true;
1676
-        $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1677
-        foreach ($wpdb_select_results as $wpdb_result) {
1678
-            // type cast stdClass as array
1679
-            $wpdb_result = (array)$wpdb_result;
1680
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1681
-            if ($this->has_primary_key_field()) {
1682
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1683
-            } else {
1684
-                // 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)
1685
-                $main_table_pk_value = null;
1686
-            }
1687
-            // 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
1688
-            // 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
1689
-            if (count($tables) > 1) {
1690
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1691
-                // in that table, and so we'll want to insert one
1692
-                foreach ($tables as $table_obj) {
1693
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1694
-                    // if there is no private key for this table on the results, it means there's no entry
1695
-                    // in this table, right? so insert a row in the current table, using any fields available
1696
-                    if (
1697
-                    ! (array_key_exists($this_table_pk_column, $wpdb_result)
1698
-                       && $wpdb_result[ $this_table_pk_column ])
1699
-                    ) {
1700
-                        $success = $this->_insert_into_specific_table(
1701
-                            $table_obj,
1702
-                            $fields_n_values,
1703
-                            $main_table_pk_value
1704
-                        );
1705
-                        // if we died here, report the error
1706
-                        if (! $success) {
1707
-                            return false;
1708
-                        }
1709
-                    }
1710
-                }
1711
-            }
1712
-            //              //and now check that if we have cached any models by that ID on the model, that
1713
-            //              //they also get updated properly
1714
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1715
-            //              if( $model_object ){
1716
-            //                  foreach( $fields_n_values as $field => $value ){
1717
-            //                      $model_object->set($field, $value);
1718
-            // let's make sure default_where strategy is followed now
1719
-            $this->_ignore_where_strategy = false;
1720
-        }
1721
-        // if we want to keep model objects in sync, AND
1722
-        // if this wasn't called from a model object (to update itself)
1723
-        // then we want to make sure we keep all the existing
1724
-        // model objects in sync with the db
1725
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1726
-            if ($this->has_primary_key_field()) {
1727
-                $model_objs_affected_ids = $this->get_col($query_params);
1728
-            } else {
1729
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1730
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params);
1731
-                $model_objs_affected_ids     = [];
1732
-                foreach ($models_affected_key_columns as $row) {
1733
-                    $combined_index_key                             = $this->get_index_primary_key_string($row);
1734
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1735
-                }
1736
-            }
1737
-            if (! $model_objs_affected_ids) {
1738
-                // wait wait wait- if nothing was affected let's stop here
1739
-                return 0;
1740
-            }
1741
-            foreach ($model_objs_affected_ids as $id) {
1742
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1743
-                if ($model_obj_in_entity_map) {
1744
-                    foreach ($fields_n_values as $field => $new_value) {
1745
-                        $model_obj_in_entity_map->set($field, $new_value);
1746
-                    }
1747
-                }
1748
-            }
1749
-            // if there is a primary key on this model, we can now do a slight optimization
1750
-            if ($this->has_primary_key_field()) {
1751
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1752
-                $query_params = [
1753
-                    [$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1754
-                    'limit'                    => count($model_objs_affected_ids),
1755
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1756
-                ];
1757
-            }
1758
-        }
1759
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1760
-        $SQL              = "UPDATE "
1761
-                            . $model_query_info->get_full_join_sql()
1762
-                            . " SET "
1763
-                            . $this->_construct_update_sql($fields_n_values)
1764
-                            . $model_query_info->get_where_sql(
1765
-            );// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1766
-        $rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1767
-        /**
1768
-         * Action called after a model update call has been made.
1769
-         *
1770
-         * @param EEM_Base $model
1771
-         * @param array    $fields_n_values the updated fields and their new values
1772
-         * @param array    $query_params
1773
-         * @param int      $rows_affected
1774
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1775
-         */
1776
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1777
-        return $rows_affected;// how many supposedly got updated
1778
-    }
1779
-
1780
-
1781
-    /**
1782
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1783
-     * are teh values of the field specified (or by default the primary key field)
1784
-     * that matched the query params. Note that you should pass the name of the
1785
-     * model FIELD, not the database table's column name.
1786
-     *
1787
-     * @param array  $query_params
1788
-     * @param string $field_to_select
1789
-     * @return array just like $wpdb->get_col()
1790
-     * @throws EE_Error
1791
-     * @throws ReflectionException
1792
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1793
-     */
1794
-    public function get_col($query_params = [], $field_to_select = null)
1795
-    {
1796
-        if ($field_to_select) {
1797
-            $field = $this->field_settings_for($field_to_select);
1798
-        } elseif ($this->has_primary_key_field()) {
1799
-            $field = $this->get_primary_key_field();
1800
-        } else {
1801
-            $field_settings = $this->field_settings();
1802
-            // no primary key, just grab the first column
1803
-            $field = reset($field_settings);
1804
-        }
1805
-        $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1806
-        $select_expressions = $field->get_qualified_column();
1807
-        $SQL                =
1808
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1809
-        return $this->_do_wpdb_query('get_col', [$SQL]);
1810
-    }
1811
-
1812
-
1813
-    /**
1814
-     * Returns a single column value for a single row from the database
1815
-     *
1816
-     * @param array  $query_params
1817
-     * @param string $field_to_select
1818
-     * @return string
1819
-     * @throws EE_Error
1820
-     * @throws ReflectionException
1821
-     * @see EEM_Base::get_col()
1822
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1823
-     */
1824
-    public function get_var($query_params = [], $field_to_select = null)
1825
-    {
1826
-        $query_params['limit'] = 1;
1827
-        $col                   = $this->get_col($query_params, $field_to_select);
1828
-        if (! empty($col)) {
1829
-            return reset($col);
1830
-        }
1831
-        return null;
1832
-    }
1833
-
1834
-
1835
-    /**
1836
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1837
-     * time?', Question.desc='what do you think?'..." Values are filtered through wpdb->prepare to avoid against SQL
1838
-     * injection, but currently no further filtering is done
1839
-     *
1840
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1841
-     *                               be updated to in the DB
1842
-     * @return string of SQL
1843
-     * @throws EE_Error
1844
-     * @global      $wpdb
1845
-     */
1846
-    public function _construct_update_sql($fields_n_values)
1847
-    {
1848
-        global $wpdb;
1849
-        $cols_n_values = [];
1850
-        foreach ($fields_n_values as $field_name => $value) {
1851
-            $field_obj = $this->field_settings_for($field_name);
1852
-            // if the value is NULL, we want to assign the value to that.
1853
-            // wpdb->prepare doesn't really handle that properly
1854
-            $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1855
-            $value_sql       = $prepared_value === null ? 'NULL'
1856
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1857
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1858
-        }
1859
-        return implode(",", $cols_n_values);
1860
-    }
1861
-
1862
-
1863
-    /**
1864
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1865
-     * Performs a HARD delete, meaning the database row should always be removed,
1866
-     * not just have a flag field on it switched
1867
-     * Wrapper for EEM_Base::delete_permanently()
1868
-     *
1869
-     * @param mixed   $id
1870
-     * @param boolean $allow_blocking
1871
-     * @return int the number of rows deleted
1872
-     * @throws EE_Error
1873
-     * @throws ReflectionException
1874
-     */
1875
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1876
-    {
1877
-        return $this->delete_permanently(
1878
-            [
1879
-                [$this->get_primary_key_field()->get_name() => $id],
1880
-                'limit' => 1,
1881
-            ],
1882
-            $allow_blocking
1883
-        );
1884
-    }
1885
-
1886
-
1887
-    /**
1888
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1889
-     * Wrapper for EEM_Base::delete()
1890
-     *
1891
-     * @param mixed   $id
1892
-     * @param boolean $allow_blocking
1893
-     * @return int the number of rows deleted
1894
-     * @throws EE_Error
1895
-     * @throws ReflectionException
1896
-     */
1897
-    public function delete_by_ID($id, $allow_blocking = true)
1898
-    {
1899
-        return $this->delete(
1900
-            [
1901
-                [$this->get_primary_key_field()->get_name() => $id],
1902
-                'limit' => 1,
1903
-            ],
1904
-            $allow_blocking
1905
-        );
1906
-    }
1907
-
1908
-
1909
-    /**
1910
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1911
-     * meaning if the model has a field that indicates its been "trashed" or
1912
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1913
-     *
1914
-     * @param array   $query_params
1915
-     * @param boolean $allow_blocking
1916
-     * @return int how many rows got deleted
1917
-     * @throws EE_Error
1918
-     * @throws ReflectionException
1919
-     * @see EEM_Base::delete_permanently
1920
-     */
1921
-    public function delete($query_params, $allow_blocking = true)
1922
-    {
1923
-        return $this->delete_permanently($query_params, $allow_blocking);
1924
-    }
1925
-
1926
-
1927
-    /**
1928
-     * Deletes the model objects that meet the query params. Note: this method is overridden
1929
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1930
-     * as archived, not actually deleted
1931
-     *
1932
-     * @param array   $query_params
1933
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1934
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1935
-     *                                deletes regardless of other objects which may depend on it. Its generally
1936
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1937
-     *                                DB
1938
-     * @return int how many rows got deleted
1939
-     * @throws EE_Error
1940
-     * @throws ReflectionException
1941
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1942
-     */
1943
-    public function delete_permanently($query_params, $allow_blocking = true)
1944
-    {
1945
-        /**
1946
-         * Action called just before performing a real deletion query. You can use the
1947
-         * model and its $query_params to find exactly which items will be deleted
1948
-         *
1949
-         * @param EEM_Base $model
1950
-         * @param array    $query_params
1951
-         * @param boolean  $allow_blocking whether or not to allow related model objects
1952
-         *                                 to block (prevent) this deletion
1953
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1954
-         */
1955
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1956
-        // some MySQL databases may be running safe mode, which may restrict
1957
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
1958
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
1959
-        // to delete them
1960
-        $items_for_deletion           = $this->_get_all_wpdb_results($query_params);
1961
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1962
-        $deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
1963
-            $columns_and_ids_for_deleting
1964
-        );
1965
-        /**
1966
-         * Allows client code to act on the items being deleted before the query is actually executed.
1967
-         *
1968
-         * @param EEM_Base $this                            The model instance being acted on.
1969
-         * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
1970
-         * @param bool     $allow_blocking                  @see param description in method phpdoc block.
1971
-         * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
1972
-         *                                                  derived from the incoming query parameters.
1973
-         * @see details on the structure of this array in the phpdocs
1974
-         *                                                  for the `_get_ids_for_delete_method`
1975
-         *
1976
-         */
1977
-        do_action(
1978
-            'AHEE__EEM_Base__delete__before_query',
1979
-            $this,
1980
-            $query_params,
1981
-            $allow_blocking,
1982
-            $columns_and_ids_for_deleting
1983
-        );
1984
-        if ($deletion_where_query_part) {
1985
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
1986
-            $table_aliases    = array_keys($this->_tables);
1987
-            $SQL              = "DELETE "
1988
-                                . implode(", ", $table_aliases)
1989
-                                . " FROM "
1990
-                                . $model_query_info->get_full_join_sql()
1991
-                                . " WHERE "
1992
-                                . $deletion_where_query_part;
1993
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
1994
-        } else {
1995
-            $rows_deleted = 0;
1996
-        }
1997
-
1998
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
1999
-        // there was no error with the delete query.
2000
-        if (
2001
-            $this->has_primary_key_field()
2002
-            && $rows_deleted !== false
2003
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2004
-        ) {
2005
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2006
-            foreach ($ids_for_removal as $id) {
2007
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2008
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2009
-                }
2010
-            }
2011
-
2012
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2013
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2014
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2015
-            // (although it is possible).
2016
-            // Note this can be skipped by using the provided filter and returning false.
2017
-            if (
2018
-            apply_filters(
2019
-                'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2020
-                ! $this instanceof EEM_Extra_Meta,
2021
-                $this
2022
-            )
2023
-            ) {
2024
-                EEM_Extra_Meta::instance()->delete_permanently(
2025
-                    [
2026
-                        0 => [
2027
-                            'EXM_type' => $this->get_this_model_name(),
2028
-                            'OBJ_ID'   => [
2029
-                                'IN',
2030
-                                $ids_for_removal,
2031
-                            ],
2032
-                        ],
2033
-                    ]
2034
-                );
2035
-            }
2036
-        }
2037
-
2038
-        /**
2039
-         * Action called just after performing a real deletion query. Although at this point the
2040
-         * items should have been deleted
2041
-         *
2042
-         * @param EEM_Base $model
2043
-         * @param array    $query_params
2044
-         * @param int      $rows_deleted
2045
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2046
-         */
2047
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2048
-        return $rows_deleted;// how many supposedly got deleted
2049
-    }
2050
-
2051
-
2052
-    /**
2053
-     * Checks all the relations that throw error messages when there are blocking related objects
2054
-     * for related model objects. If there are any related model objects on those relations,
2055
-     * adds an EE_Error, and return true
2056
-     *
2057
-     * @param EE_Base_Class|int $this_model_obj_or_id
2058
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2059
-     *                                                 should be ignored when determining whether there are related
2060
-     *                                                 model objects which block this model object's deletion. Useful
2061
-     *                                                 if you know A is related to B and are considering deleting A,
2062
-     *                                                 but want to see if A has any other objects blocking its deletion
2063
-     *                                                 before removing the relation between A and B
2064
-     * @return boolean
2065
-     * @throws EE_Error
2066
-     * @throws ReflectionException
2067
-     */
2068
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2069
-    {
2070
-        // first, if $ignore_this_model_obj was supplied, get its model
2071
-        if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2072
-            $ignored_model = $ignore_this_model_obj->get_model();
2073
-        } else {
2074
-            $ignored_model = null;
2075
-        }
2076
-        // now check all the relations of $this_model_obj_or_id and see if there
2077
-        // are any related model objects blocking it?
2078
-        $is_blocked = false;
2079
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2080
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2081
-                // if $ignore_this_model_obj was supplied, then for the query
2082
-                // on that model needs to be told to ignore $ignore_this_model_obj
2083
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2084
-                    $related_model_objects = $relation_obj->get_all_related(
2085
-                        $this_model_obj_or_id,
2086
-                        [
2087
-                            [
2088
-                                $ignored_model->get_primary_key_field()->get_name() => [
2089
-                                    '!=',
2090
-                                    $ignore_this_model_obj->ID(),
2091
-                                ],
2092
-                            ],
2093
-                        ]
2094
-                    );
2095
-                } else {
2096
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2097
-                }
2098
-                if ($related_model_objects) {
2099
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2100
-                    $is_blocked = true;
2101
-                }
2102
-            }
2103
-        }
2104
-        return $is_blocked;
2105
-    }
2106
-
2107
-
2108
-    /**
2109
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2110
-     *
2111
-     * @param array $row_results_for_deleting
2112
-     * @param bool  $allow_blocking
2113
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2114
-     *                              model DOES have a primary_key_field, then the array will be a simple single
2115
-     *                              dimension array where the key is the fully qualified primary key column and the
2116
-     *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2117
-     *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2118
-     *                              be a two dimensional array where each element is a group of columns and values that
2119
-     *                              get deleted. Example: array(
2120
-     *                              0 => array(
2121
-     *                              'Term_Relationship.object_id' => 1
2122
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2123
-     *                              ),
2124
-     *                              1 => array(
2125
-     *                              'Term_Relationship.object_id' => 1
2126
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2127
-     *                              )
2128
-     *                              )
2129
-     * @throws EE_Error
2130
-     * @throws ReflectionException
2131
-     */
2132
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2133
-    {
2134
-        $ids_to_delete_indexed_by_column = [];
2135
-        if ($this->has_primary_key_field()) {
2136
-            $primary_table                   = $this->_get_main_table();
2137
-            $ids_to_delete_indexed_by_column = $query = [];
2138
-            foreach ($row_results_for_deleting as $item_to_delete) {
2139
-                // before we mark this item for deletion,
2140
-                // make sure there's no related entities blocking its deletion (if we're checking)
2141
-                if (
2142
-                    $allow_blocking
2143
-                    && $this->delete_is_blocked_by_related_models(
2144
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2145
-                    )
2146
-                ) {
2147
-                    continue;
2148
-                }
2149
-                // primary table deletes
2150
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2151
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2152
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2153
-                }
2154
-            }
2155
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2156
-            $fields = $this->get_combined_primary_key_fields();
2157
-            foreach ($row_results_for_deleting as $item_to_delete) {
2158
-                $ids_to_delete_indexed_by_column_for_row = [];
2159
-                foreach ($fields as $cpk_field) {
2160
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2161
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2162
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2163
-                    }
2164
-                }
2165
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2166
-            }
2167
-        } else {
2168
-            // so there's no primary key and no combined key...
2169
-            // sorry, can't help you
2170
-            throw new EE_Error(
2171
-                sprintf(
2172
-                    esc_html__(
2173
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2174
-                        "event_espresso"
2175
-                    ),
2176
-                    get_class($this)
2177
-                )
2178
-            );
2179
-        }
2180
-        return $ids_to_delete_indexed_by_column;
2181
-    }
2182
-
2183
-
2184
-    /**
2185
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2186
-     * the corresponding query_part for the query performing the delete.
2187
-     *
2188
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2189
-     * @return string
2190
-     * @throws EE_Error
2191
-     */
2192
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2193
-    {
2194
-        $query_part = '';
2195
-        if (empty($ids_to_delete_indexed_by_column)) {
2196
-            return $query_part;
2197
-        } elseif ($this->has_primary_key_field()) {
2198
-            $query = [];
2199
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2200
-                // make sure we have unique $ids
2201
-                $ids     = array_unique($ids);
2202
-                $query[] = $column . ' IN(' . implode(',', $ids) . ')';
2203
-            }
2204
-            $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2205
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2206
-            $ways_to_identify_a_row = [];
2207
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2208
-                $values_for_each_combined_primary_key_for_a_row = [];
2209
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2210
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2211
-                }
2212
-                $ways_to_identify_a_row[] = '('
2213
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2214
-                                            . ')';
2215
-            }
2216
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2217
-        }
2218
-        return $query_part;
2219
-    }
2220
-
2221
-
2222
-    /**
2223
-     * Gets the model field by the fully qualified name
2224
-     *
2225
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2226
-     * @return EE_Model_Field_Base
2227
-     * @throws EE_Error
2228
-     */
2229
-    public function get_field_by_column($qualified_column_name)
2230
-    {
2231
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2232
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2233
-                return $field_obj;
2234
-            }
2235
-        }
2236
-        throw new EE_Error(
2237
-            sprintf(
2238
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2239
-                $this->get_this_model_name(),
2240
-                $qualified_column_name
2241
-            )
2242
-        );
2243
-    }
2244
-
2245
-
2246
-    /**
2247
-     * Count all the rows that match criteria the model query params.
2248
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2249
-     * column
2250
-     *
2251
-     * @param array  $query_params
2252
-     * @param string $field_to_count field on model to count by (not column name)
2253
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2254
-     *                               that by the setting $distinct to TRUE;
2255
-     * @return int
2256
-     * @throws EE_Error
2257
-     * @throws ReflectionException
2258
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2259
-     */
2260
-    public function count($query_params = [], $field_to_count = null, $distinct = false)
2261
-    {
2262
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2263
-        if ($field_to_count) {
2264
-            $field_obj       = $this->field_settings_for($field_to_count);
2265
-            $column_to_count = $field_obj->get_qualified_column();
2266
-        } elseif ($this->has_primary_key_field()) {
2267
-            $pk_field_obj    = $this->get_primary_key_field();
2268
-            $column_to_count = $pk_field_obj->get_qualified_column();
2269
-        } else {
2270
-            // there's no primary key
2271
-            // if we're counting distinct items, and there's no primary key,
2272
-            // we need to list out the columns for distinction;
2273
-            // otherwise we can just use star
2274
-            if ($distinct) {
2275
-                $columns_to_use = [];
2276
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2277
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2278
-                }
2279
-                $column_to_count = implode(',', $columns_to_use);
2280
-            } else {
2281
-                $column_to_count = '*';
2282
-            }
2283
-        }
2284
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2285
-        $SQL             =
2286
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2287
-        return (int)$this->_do_wpdb_query('get_var', [$SQL]);
2288
-    }
2289
-
2290
-
2291
-    /**
2292
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2293
-     *
2294
-     * @param array  $query_params
2295
-     * @param string $field_to_sum name of field (array key in $_fields array)
2296
-     * @return float
2297
-     * @throws EE_Error
2298
-     * @throws ReflectionException
2299
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2300
-     */
2301
-    public function sum($query_params, $field_to_sum = null)
2302
-    {
2303
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2304
-        if ($field_to_sum) {
2305
-            $field_obj = $this->field_settings_for($field_to_sum);
2306
-        } else {
2307
-            $field_obj = $this->get_primary_key_field();
2308
-        }
2309
-        $column_to_count = $field_obj->get_qualified_column();
2310
-        $SQL             =
2311
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2312
-        $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2313
-        $data_type       = $field_obj->get_wpdb_data_type();
2314
-        if ($data_type === '%d' || $data_type === '%s') {
2315
-            return (float)$return_value;
2316
-        }
2317
-        // must be %f
2318
-        return (float)$return_value;
2319
-    }
2320
-
2321
-
2322
-    /**
2323
-     * Just calls the specified method on $wpdb with the given arguments
2324
-     * Consolidates a little extra error handling code
2325
-     *
2326
-     * @param string $wpdb_method
2327
-     * @param array  $arguments_to_provide
2328
-     * @return mixed
2329
-     * @throws EE_Error
2330
-     * @global wpdb  $wpdb
2331
-     */
2332
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2333
-    {
2334
-        // if we're in maintenance mode level 2, DON'T run any queries
2335
-        // because level 2 indicates the database needs updating and
2336
-        // is probably out of sync with the code
2337
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2338
-            throw new EE_Error(
2339
-                sprintf(
2340
-                    esc_html__(
2341
-                        "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.",
2342
-                        "event_espresso"
2343
-                    )
2344
-                )
2345
-            );
2346
-        }
2347
-        global $wpdb;
2348
-        if (! method_exists($wpdb, $wpdb_method)) {
2349
-            throw new EE_Error(
2350
-                sprintf(
2351
-                    esc_html__(
2352
-                        'There is no method named "%s" on Wordpress\' $wpdb object',
2353
-                        'event_espresso'
2354
-                    ),
2355
-                    $wpdb_method
2356
-                )
2357
-            );
2358
-        }
2359
-        $old_show_errors_value = $wpdb->show_errors;
2360
-        if (WP_DEBUG) {
2361
-            $wpdb->show_errors(false);
2362
-        }
2363
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2364
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2365
-        if (WP_DEBUG) {
2366
-            $wpdb->show_errors($old_show_errors_value);
2367
-            if (! empty($wpdb->last_error)) {
2368
-                throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2369
-            }
2370
-            if ($result === false) {
2371
-                throw new EE_Error(
2372
-                    sprintf(
2373
-                        esc_html__(
2374
-                            'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2375
-                            'event_espresso'
2376
-                        ),
2377
-                        $wpdb_method,
2378
-                        var_export($arguments_to_provide, true)
2379
-                    )
2380
-                );
2381
-            }
2382
-        } elseif ($result === false) {
2383
-            EE_Error::add_error(
2384
-                sprintf(
2385
-                    esc_html__(
2386
-                        '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"',
2387
-                        'event_espresso'
2388
-                    ),
2389
-                    $wpdb_method,
2390
-                    var_export($arguments_to_provide, true),
2391
-                    $wpdb->last_error
2392
-                ),
2393
-                __FILE__,
2394
-                __FUNCTION__,
2395
-                __LINE__
2396
-            );
2397
-        }
2398
-        return $result;
2399
-    }
2400
-
2401
-
2402
-    /**
2403
-     * Attempts to run the indicated WPDB method with the provided arguments,
2404
-     * and if there's an error tries to verify the DB is correct. Uses
2405
-     * the static property EEM_Base::$_db_verification_level to determine whether
2406
-     * we should try to fix the EE core db, the addons, or just give up
2407
-     *
2408
-     * @param string $wpdb_method
2409
-     * @param array  $arguments_to_provide
2410
-     * @return mixed
2411
-     * @throws EE_Error
2412
-     */
2413
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2414
-    {
2415
-        global $wpdb;
2416
-        $wpdb->last_error = null;
2417
-        $result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2418
-        // was there an error running the query? but we don't care on new activations
2419
-        // (we're going to setup the DB anyway on new activations)
2420
-        if (
2421
-            ($result === false || ! empty($wpdb->last_error))
2422
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2423
-        ) {
2424
-            switch (EEM_Base::$_db_verification_level) {
2425
-                case EEM_Base::db_verified_none:
2426
-                    // let's double-check core's DB
2427
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2428
-                    break;
2429
-                case EEM_Base::db_verified_core:
2430
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2431
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2432
-                    break;
2433
-                case EEM_Base::db_verified_addons:
2434
-                    // ummmm... you in trouble
2435
-                    return $result;
2436
-            }
2437
-            if (! empty($error_message)) {
2438
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2439
-                trigger_error($error_message);
2440
-            }
2441
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2442
-        }
2443
-        return $result;
2444
-    }
2445
-
2446
-
2447
-    /**
2448
-     * Verifies the EE core database is up-to-date and records that we've done it on
2449
-     * EEM_Base::$_db_verification_level
2450
-     *
2451
-     * @param string $wpdb_method
2452
-     * @param array  $arguments_to_provide
2453
-     * @return string
2454
-     * @throws EE_Error
2455
-     */
2456
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2457
-    {
2458
-        global $wpdb;
2459
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2460
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2461
-        $error_message                    = sprintf(
2462
-            esc_html__(
2463
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2464
-                'event_espresso'
2465
-            ),
2466
-            $wpdb->last_error,
2467
-            $wpdb_method,
2468
-            wp_json_encode($arguments_to_provide)
2469
-        );
2470
-        EE_System::instance()->initialize_db_if_no_migrations_required();
2471
-        return $error_message;
2472
-    }
2473
-
2474
-
2475
-    /**
2476
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2477
-     * EEM_Base::$_db_verification_level
2478
-     *
2479
-     * @param $wpdb_method
2480
-     * @param $arguments_to_provide
2481
-     * @return string
2482
-     * @throws EE_Error
2483
-     */
2484
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2485
-    {
2486
-        global $wpdb;
2487
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2488
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2489
-        $error_message                    = sprintf(
2490
-            esc_html__(
2491
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2492
-                'event_espresso'
2493
-            ),
2494
-            $wpdb->last_error,
2495
-            $wpdb_method,
2496
-            wp_json_encode($arguments_to_provide)
2497
-        );
2498
-        EE_System::instance()->initialize_addons();
2499
-        return $error_message;
2500
-    }
2501
-
2502
-
2503
-    /**
2504
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2505
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2506
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2507
-     * ..."
2508
-     *
2509
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2510
-     * @return string
2511
-     */
2512
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2513
-    {
2514
-        return " FROM " . $model_query_info->get_full_join_sql() .
2515
-               $model_query_info->get_where_sql() .
2516
-               $model_query_info->get_group_by_sql() .
2517
-               $model_query_info->get_having_sql() .
2518
-               $model_query_info->get_order_by_sql() .
2519
-               $model_query_info->get_limit_sql();
2520
-    }
2521
-
2522
-
2523
-    /**
2524
-     * Set to easily debug the next X queries ran from this model.
2525
-     *
2526
-     * @param int $count
2527
-     */
2528
-    public function show_next_x_db_queries($count = 1)
2529
-    {
2530
-        $this->_show_next_x_db_queries = $count;
2531
-    }
2532
-
2533
-
2534
-    /**
2535
-     * @param $sql_query
2536
-     */
2537
-    public function show_db_query_if_previously_requested($sql_query)
2538
-    {
2539
-        if ($this->_show_next_x_db_queries > 0) {
2540
-            echo $sql_query;
2541
-            $this->_show_next_x_db_queries--;
2542
-        }
2543
-    }
2544
-
2545
-
2546
-    /**
2547
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2548
-     * There are the 3 cases:
2549
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2550
-     * $otherModelObject has no ID, it is first saved.
2551
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2552
-     * has no ID, it is first saved.
2553
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2554
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2555
-     * join table
2556
-     *
2557
-     * @param EE_Base_Class                     /int $thisModelObject
2558
-     * @param EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2559
-     * @param string $relationName                     , key in EEM_Base::_relations
2560
-     *                                                 an attendee to a group, you also want to specify which role they
2561
-     *                                                 will have in that group. So you would use this parameter to
2562
-     *                                                 specify array('role-column-name'=>'role-id')
2563
-     * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2564
-     *                                                 to for relation to methods that allow you to further specify
2565
-     *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2566
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2567
-     *                                                 because these will be inserted in any new rows created as well.
2568
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2569
-     * @throws EE_Error
2570
-     */
2571
-    public function add_relationship_to(
2572
-        $id_or_obj,
2573
-        $other_model_id_or_obj,
2574
-        $relationName,
2575
-        $extra_join_model_fields_n_values = []
2576
-    ) {
2577
-        $relation_obj = $this->related_settings_for($relationName);
2578
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2579
-    }
2580
-
2581
-
2582
-    /**
2583
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2584
-     * There are the 3 cases:
2585
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2586
-     * error
2587
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2588
-     * an error
2589
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2590
-     *
2591
-     * @param EE_Base_Class /int $id_or_obj
2592
-     * @param EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2593
-     * @param string $relationName key in EEM_Base::_relations
2594
-     * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2595
-     *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2596
-     *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2597
-     *                             because these will be inserted in any new rows created as well.
2598
-     * @return boolean of success
2599
-     * @throws EE_Error
2600
-     */
2601
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2602
-    {
2603
-        $relation_obj = $this->related_settings_for($relationName);
2604
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2605
-    }
2606
-
2607
-
2608
-    /**
2609
-     * @param mixed  $id_or_obj
2610
-     * @param string $relationName
2611
-     * @param array  $where_query_params
2612
-     * @param EE_Base_Class[] objects to which relations were removed
2613
-     * @return EE_Base_Class[]
2614
-     * @throws EE_Error
2615
-     */
2616
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2617
-    {
2618
-        $relation_obj = $this->related_settings_for($relationName);
2619
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2620
-    }
2621
-
2622
-
2623
-    /**
2624
-     * Gets all the related items of the specified $model_name, using $query_params.
2625
-     * Note: by default, we remove the "default query params"
2626
-     * because we want to get even deleted items etc.
2627
-     *
2628
-     * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2629
-     * @param string $model_name   like 'Event', 'Registration', etc. always singular
2630
-     * @param array  $query_params see github link below for more info
2631
-     * @return EE_Base_Class[]
2632
-     * @throws EE_Error
2633
-     * @throws ReflectionException
2634
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2635
-     */
2636
-    public function get_all_related($id_or_obj, $model_name, $query_params = null)
2637
-    {
2638
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2639
-        $relation_settings = $this->related_settings_for($model_name);
2640
-        return $relation_settings->get_all_related($model_obj, $query_params);
2641
-    }
2642
-
2643
-
2644
-    /**
2645
-     * Deletes all the model objects across the relation indicated by $model_name
2646
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2647
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2648
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2649
-     *
2650
-     * @param EE_Base_Class|int|string $id_or_obj
2651
-     * @param string                   $model_name
2652
-     * @param array                    $query_params
2653
-     * @return int how many deleted
2654
-     * @throws EE_Error
2655
-     * @throws ReflectionException
2656
-     */
2657
-    public function delete_related($id_or_obj, $model_name, $query_params = [])
2658
-    {
2659
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2660
-        $relation_settings = $this->related_settings_for($model_name);
2661
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2662
-    }
2663
-
2664
-
2665
-    /**
2666
-     * Hard deletes all the model objects across the relation indicated by $model_name
2667
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2668
-     * the model objects can't be hard deleted because of blocking related model objects,
2669
-     * just does a soft-delete on them instead.
2670
-     *
2671
-     * @param EE_Base_Class|int|string $id_or_obj
2672
-     * @param string                   $model_name
2673
-     * @param array                    $query_params
2674
-     * @return int how many deleted
2675
-     * @throws EE_Error
2676
-     * @throws ReflectionException
2677
-     */
2678
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2679
-    {
2680
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2681
-        $relation_settings = $this->related_settings_for($model_name);
2682
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2683
-    }
2684
-
2685
-
2686
-    /**
2687
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2688
-     * unless otherwise specified in the $query_params
2689
-     *
2690
-     * @param int             /EE_Base_Class $id_or_obj
2691
-     * @param string $model_name     like 'Event', or 'Registration'
2692
-     * @param array  $query_params
2693
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2694
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2695
-     *                               that by the setting $distinct to TRUE;
2696
-     * @return int
2697
-     * @throws EE_Error
2698
-     * @throws ReflectionException
2699
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2700
-     */
2701
-    public function count_related(
2702
-        $id_or_obj,
2703
-        $model_name,
2704
-        $query_params = [],
2705
-        $field_to_count = null,
2706
-        $distinct = false
2707
-    ) {
2708
-        $related_model = $this->get_related_model_obj($model_name);
2709
-        // we're just going to use the query params on the related model's normal get_all query,
2710
-        // except add a condition to say to match the current mod
2711
-        if (! isset($query_params['default_where_conditions'])) {
2712
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2713
-        }
2714
-        $this_model_name                                                 = $this->get_this_model_name();
2715
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2716
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2717
-        return $related_model->count($query_params, $field_to_count, $distinct);
2718
-    }
2719
-
2720
-
2721
-    /**
2722
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2723
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2724
-     *
2725
-     * @param int           /EE_Base_Class $id_or_obj
2726
-     * @param string $model_name   like 'Event', or 'Registration'
2727
-     * @param array  $query_params
2728
-     * @param string $field_to_sum name of field to count by. By default, uses primary key
2729
-     * @return float
2730
-     * @throws EE_Error
2731
-     * @throws ReflectionException
2732
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2733
-     */
2734
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2735
-    {
2736
-        $related_model = $this->get_related_model_obj($model_name);
2737
-        if (! is_array($query_params)) {
2738
-            EE_Error::doing_it_wrong(
2739
-                'EEM_Base::sum_related',
2740
-                sprintf(
2741
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2742
-                    gettype($query_params)
2743
-                ),
2744
-                '4.6.0'
2745
-            );
2746
-            $query_params = [];
2747
-        }
2748
-        // we're just going to use the query params on the related model's normal get_all query,
2749
-        // except add a condition to say to match the current mod
2750
-        if (! isset($query_params['default_where_conditions'])) {
2751
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2752
-        }
2753
-        $this_model_name                                                 = $this->get_this_model_name();
2754
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2755
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2756
-        return $related_model->sum($query_params, $field_to_sum);
2757
-    }
2758
-
2759
-
2760
-    /**
2761
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2762
-     * $modelObject
2763
-     *
2764
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2765
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2766
-     * @param array               $query_params     see github link below for more info
2767
-     * @return EE_Base_Class
2768
-     * @throws EE_Error
2769
-     * @throws ReflectionException
2770
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2771
-     */
2772
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2773
-    {
2774
-        $query_params['limit'] = 1;
2775
-        $results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2776
-        if ($results) {
2777
-            return array_shift($results);
2778
-        }
2779
-        return null;
2780
-    }
2781
-
2782
-
2783
-    /**
2784
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2785
-     *
2786
-     * @return string
2787
-     */
2788
-    public function get_this_model_name()
2789
-    {
2790
-        return str_replace("EEM_", "", get_class($this));
2791
-    }
2792
-
2793
-
2794
-    /**
2795
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2796
-     *
2797
-     * @return EE_Any_Foreign_Model_Name_Field
2798
-     * @throws EE_Error
2799
-     */
2800
-    public function get_field_containing_related_model_name()
2801
-    {
2802
-        foreach ($this->field_settings(true) as $field) {
2803
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2804
-                $field_with_model_name = $field;
2805
-            }
2806
-        }
2807
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2808
-            throw new EE_Error(
2809
-                sprintf(
2810
-                    esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2811
-                    $this->get_this_model_name()
2812
-                )
2813
-            );
2814
-        }
2815
-        return $field_with_model_name;
2816
-    }
2817
-
2818
-
2819
-    /**
2820
-     * Inserts a new entry into the database, for each table.
2821
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2822
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2823
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2824
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2825
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2826
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2827
-     *
2828
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2829
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2830
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2831
-     *                              of EEM_Base)
2832
-     * @return int|string new primary key on main table that got inserted
2833
-     * @throws EE_Error
2834
-     * @throws ReflectionException
2835
-     */
2836
-    public function insert($field_n_values)
2837
-    {
2838
-        /**
2839
-         * Filters the fields and their values before inserting an item using the models
2840
-         *
2841
-         * @param array    $fields_n_values keys are the fields and values are their new values
2842
-         * @param EEM_Base $model           the model used
2843
-         */
2844
-        $field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2845
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2846
-            $main_table = $this->_get_main_table();
2847
-            $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2848
-            if ($new_id !== false) {
2849
-                foreach ($this->_get_other_tables() as $other_table) {
2850
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2851
-                }
2852
-            }
2853
-            /**
2854
-             * Done just after attempting to insert a new model object
2855
-             *
2856
-             * @param EEM_Base $model           used
2857
-             * @param array    $fields_n_values fields and their values
2858
-             * @param int|string the              ID of the newly-inserted model object
2859
-             */
2860
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2861
-            return $new_id;
2862
-        }
2863
-        return false;
2864
-    }
2865
-
2866
-
2867
-    /**
2868
-     * Checks that the result would satisfy the unique indexes on this model
2869
-     *
2870
-     * @param array  $field_n_values
2871
-     * @param string $action
2872
-     * @return boolean
2873
-     * @throws EE_Error
2874
-     * @throws ReflectionException
2875
-     */
2876
-    protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2877
-    {
2878
-        foreach ($this->unique_indexes() as $index_name => $index) {
2879
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2880
-            if ($this->exists([$uniqueness_where_params])) {
2881
-                EE_Error::add_error(
2882
-                    sprintf(
2883
-                        esc_html__(
2884
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2885
-                            "event_espresso"
2886
-                        ),
2887
-                        $action,
2888
-                        $this->_get_class_name(),
2889
-                        $index_name,
2890
-                        implode(",", $index->field_names()),
2891
-                        http_build_query($uniqueness_where_params)
2892
-                    ),
2893
-                    __FILE__,
2894
-                    __FUNCTION__,
2895
-                    __LINE__
2896
-                );
2897
-                return false;
2898
-            }
2899
-        }
2900
-        return true;
2901
-    }
2902
-
2903
-
2904
-    /**
2905
-     * Checks the database for an item that conflicts (ie, if this item were
2906
-     * saved to the DB would break some uniqueness requirement, like a primary key
2907
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
2908
-     * can be either an EE_Base_Class or an array of fields n values
2909
-     *
2910
-     * @param EE_Base_Class|array $obj_or_fields_array
2911
-     * @param boolean             $include_primary_key whether to use the model object's primary key
2912
-     *                                                 when looking for conflicts
2913
-     *                                                 (ie, if false, we ignore the model object's primary key
2914
-     *                                                 when finding "conflicts". If true, it's also considered).
2915
-     *                                                 Only works for INT primary key,
2916
-     *                                                 STRING primary keys cannot be ignored
2917
-     * @return EE_Base_Class|array
2918
-     * @throws EE_Error
2919
-     * @throws ReflectionException
2920
-     */
2921
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2922
-    {
2923
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
2924
-            $fields_n_values = $obj_or_fields_array->model_field_array();
2925
-        } elseif (is_array($obj_or_fields_array)) {
2926
-            $fields_n_values = $obj_or_fields_array;
2927
-        } else {
2928
-            throw new EE_Error(
2929
-                sprintf(
2930
-                    esc_html__(
2931
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2932
-                        "event_espresso"
2933
-                    ),
2934
-                    get_class($this),
2935
-                    $obj_or_fields_array
2936
-                )
2937
-            );
2938
-        }
2939
-        $query_params = [];
2940
-        if (
2941
-            $this->has_primary_key_field()
2942
-            && ($include_primary_key
2943
-                || $this->get_primary_key_field()
2944
-                   instanceof
2945
-                   EE_Primary_Key_String_Field)
2946
-            && isset($fields_n_values[ $this->primary_key_name() ])
2947
-        ) {
2948
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2949
-        }
2950
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2951
-            $uniqueness_where_params                              =
2952
-                array_intersect_key($fields_n_values, $unique_index->fields());
2953
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2954
-        }
2955
-        // if there is nothing to base this search on, then we shouldn't find anything
2956
-        if (empty($query_params)) {
2957
-            return [];
2958
-        }
2959
-        return $this->get_one($query_params);
2960
-    }
2961
-
2962
-
2963
-    /**
2964
-     * Like count, but is optimized and returns a boolean instead of an int
2965
-     *
2966
-     * @param array $query_params
2967
-     * @return boolean
2968
-     * @throws EE_Error
2969
-     * @throws ReflectionException
2970
-     */
2971
-    public function exists($query_params)
2972
-    {
2973
-        $query_params['limit'] = 1;
2974
-        return $this->count($query_params) > 0;
2975
-    }
2976
-
2977
-
2978
-    /**
2979
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
2980
-     *
2981
-     * @param int|string $id
2982
-     * @return boolean
2983
-     * @throws EE_Error
2984
-     * @throws ReflectionException
2985
-     */
2986
-    public function exists_by_ID($id)
2987
-    {
2988
-        return $this->exists(
2989
-            [
2990
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
2991
-                [
2992
-                    $this->primary_key_name() => $id,
2993
-                ],
2994
-            ]
2995
-        );
2996
-    }
2997
-
2998
-
2999
-    /**
3000
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3001
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3002
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3003
-     * on the main table)
3004
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3005
-     * cases where we want to call it directly rather than via insert().
3006
-     *
3007
-     * @access   protected
3008
-     * @param EE_Table_Base $table
3009
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3010
-     *                                       float
3011
-     * @param int           $new_id          for now we assume only int keys
3012
-     * @return int ID of new row inserted, or FALSE on failure
3013
-     * @throws EE_Error
3014
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3015
-     */
3016
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3017
-    {
3018
-        global $wpdb;
3019
-        $insertion_col_n_values = [];
3020
-        $format_for_insertion   = [];
3021
-        $fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3022
-        foreach ($fields_on_table as $field_name => $field_obj) {
3023
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3024
-            if ($field_obj->is_auto_increment()) {
3025
-                continue;
3026
-            }
3027
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3028
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3029
-            if ($prepared_value !== null) {
3030
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3031
-                $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3032
-            }
3033
-        }
3034
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3035
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3036
-            // so add the fk to the main table as a column
3037
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3038
-            $format_for_insertion[]                              =
3039
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3040
-        }
3041
-        // insert the new entry
3042
-        $result = $this->_do_wpdb_query(
3043
-            'insert',
3044
-            [$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3045
-        );
3046
-        if ($result === false) {
3047
-            return false;
3048
-        }
3049
-        // ok, now what do we return for the ID of the newly-inserted thing?
3050
-        if ($this->has_primary_key_field()) {
3051
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3052
-                return $wpdb->insert_id;
3053
-            }
3054
-            // it's not an auto-increment primary key, so
3055
-            // it must have been supplied
3056
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3057
-        }
3058
-        // we can't return a  primary key because there is none. instead return
3059
-        // a unique string indicating this model
3060
-        return $this->get_index_primary_key_string($fields_n_values);
3061
-    }
3062
-
3063
-
3064
-    /**
3065
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3066
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3067
-     * and there is no default, we pass it along. WPDB will take care of it)
3068
-     *
3069
-     * @param EE_Model_Field_Base $field_obj
3070
-     * @param array               $fields_n_values
3071
-     * @return mixed string|int|float depending on what the table column will be expecting
3072
-     * @throws EE_Error
3073
-     */
3074
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3075
-    {
3076
-        // if this field doesn't allow nullable, don't allow it
3077
-        if (
3078
-            ! $field_obj->is_nullable()
3079
-            && (
3080
-                ! isset($fields_n_values[ $field_obj->get_name() ])
3081
-                || $fields_n_values[ $field_obj->get_name() ] === null
3082
-            )
3083
-        ) {
3084
-            $fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3085
-        }
3086
-        $unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3087
-            ? $fields_n_values[ $field_obj->get_name() ]
3088
-            : null;
3089
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3090
-    }
3091
-
3092
-
3093
-    /**
3094
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3095
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3096
-     * the field's prepare_for_set() method.
3097
-     *
3098
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3099
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3100
-     *                                   top of file)
3101
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3102
-     *                                   $value is a custom selection
3103
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3104
-     */
3105
-    private function _prepare_value_for_use_in_db($value, $field)
3106
-    {
3107
-        if ($field && $field instanceof EE_Model_Field_Base) {
3108
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3109
-            switch ($this->_values_already_prepared_by_model_object) {
3110
-                /** @noinspection PhpMissingBreakStatementInspection */
3111
-                case self::not_prepared_by_model_object:
3112
-                    $value = $field->prepare_for_set($value);
3113
-                // purposefully left out "return"
3114
-                case self::prepared_by_model_object:
3115
-                    /** @noinspection SuspiciousAssignmentsInspection */
3116
-                    $value = $field->prepare_for_use_in_db($value);
3117
-                case self::prepared_for_use_in_db:
3118
-                    // leave the value alone
3119
-            }
3120
-            return $value;
3121
-            // phpcs:enable
3122
-        }
3123
-        return $value;
3124
-    }
3125
-
3126
-
3127
-    /**
3128
-     * Returns the main table on this model
3129
-     *
3130
-     * @return EE_Primary_Table
3131
-     * @throws EE_Error
3132
-     */
3133
-    protected function _get_main_table()
3134
-    {
3135
-        foreach ($this->_tables as $table) {
3136
-            if ($table instanceof EE_Primary_Table) {
3137
-                return $table;
3138
-            }
3139
-        }
3140
-        throw new EE_Error(
3141
-            sprintf(
3142
-                esc_html__(
3143
-                    'There are no main tables on %s. They should be added to _tables array in the constructor',
3144
-                    'event_espresso'
3145
-                ),
3146
-                get_class($this)
3147
-            )
3148
-        );
3149
-    }
3150
-
3151
-
3152
-    /**
3153
-     * table
3154
-     * returns EE_Primary_Table table name
3155
-     *
3156
-     * @return string
3157
-     * @throws EE_Error
3158
-     */
3159
-    public function table()
3160
-    {
3161
-        return $this->_get_main_table()->get_table_name();
3162
-    }
3163
-
3164
-
3165
-    /**
3166
-     * table
3167
-     * returns first EE_Secondary_Table table name
3168
-     *
3169
-     * @return string
3170
-     */
3171
-    public function second_table()
3172
-    {
3173
-        // grab second table from tables array
3174
-        $second_table = end($this->_tables);
3175
-        return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3176
-    }
3177
-
3178
-
3179
-    /**
3180
-     * get_table_obj_by_alias
3181
-     * returns table name given it's alias
3182
-     *
3183
-     * @param string $table_alias
3184
-     * @return EE_Primary_Table | EE_Secondary_Table
3185
-     */
3186
-    public function get_table_obj_by_alias($table_alias = '')
3187
-    {
3188
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3189
-    }
3190
-
3191
-
3192
-    /**
3193
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3194
-     *
3195
-     * @return EE_Secondary_Table[]
3196
-     */
3197
-    protected function _get_other_tables()
3198
-    {
3199
-        $other_tables = [];
3200
-        foreach ($this->_tables as $table_alias => $table) {
3201
-            if ($table instanceof EE_Secondary_Table) {
3202
-                $other_tables[ $table_alias ] = $table;
3203
-            }
3204
-        }
3205
-        return $other_tables;
3206
-    }
3207
-
3208
-
3209
-    /**
3210
-     * Finds all the fields that correspond to the given table
3211
-     *
3212
-     * @param string $table_alias , array key in EEM_Base::_tables
3213
-     * @return EE_Model_Field_Base[]
3214
-     */
3215
-    public function _get_fields_for_table($table_alias)
3216
-    {
3217
-        return $this->_fields[ $table_alias ];
3218
-    }
3219
-
3220
-
3221
-    /**
3222
-     * Recurses through all the where parameters, and finds all the related models we'll need
3223
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3224
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3225
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3226
-     * related Registration, Transaction, and Payment models.
3227
-     *
3228
-     * @param array $query_params see github link below for more info
3229
-     * @return EE_Model_Query_Info_Carrier
3230
-     * @throws EE_Error
3231
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3232
-     */
3233
-    public function _extract_related_models_from_query($query_params)
3234
-    {
3235
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3236
-        if (array_key_exists(0, $query_params)) {
3237
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3238
-        }
3239
-        if (array_key_exists('group_by', $query_params)) {
3240
-            if (is_array($query_params['group_by'])) {
3241
-                $this->_extract_related_models_from_sub_params_array_values(
3242
-                    $query_params['group_by'],
3243
-                    $query_info_carrier,
3244
-                    'group_by'
3245
-                );
3246
-            } elseif (! empty($query_params['group_by'])) {
3247
-                $this->_extract_related_model_info_from_query_param(
3248
-                    $query_params['group_by'],
3249
-                    $query_info_carrier,
3250
-                    'group_by'
3251
-                );
3252
-            }
3253
-        }
3254
-        if (array_key_exists('having', $query_params)) {
3255
-            $this->_extract_related_models_from_sub_params_array_keys(
3256
-                $query_params[0],
3257
-                $query_info_carrier,
3258
-                'having'
3259
-            );
3260
-        }
3261
-        if (array_key_exists('order_by', $query_params)) {
3262
-            if (is_array($query_params['order_by'])) {
3263
-                $this->_extract_related_models_from_sub_params_array_keys(
3264
-                    $query_params['order_by'],
3265
-                    $query_info_carrier,
3266
-                    'order_by'
3267
-                );
3268
-            } elseif (! empty($query_params['order_by'])) {
3269
-                $this->_extract_related_model_info_from_query_param(
3270
-                    $query_params['order_by'],
3271
-                    $query_info_carrier,
3272
-                    'order_by'
3273
-                );
3274
-            }
3275
-        }
3276
-        if (array_key_exists('force_join', $query_params)) {
3277
-            $this->_extract_related_models_from_sub_params_array_values(
3278
-                $query_params['force_join'],
3279
-                $query_info_carrier,
3280
-                'force_join'
3281
-            );
3282
-        }
3283
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3284
-        return $query_info_carrier;
3285
-    }
3286
-
3287
-
3288
-    /**
3289
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3290
-     *
3291
-     * @param array                       $sub_query_params
3292
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3293
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3294
-     * @return EE_Model_Query_Info_Carrier
3295
-     * @throws EE_Error
3296
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3297
-     */
3298
-    private function _extract_related_models_from_sub_params_array_keys(
3299
-        $sub_query_params,
3300
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3301
-        $query_param_type
3302
-    ) {
3303
-        if (! empty($sub_query_params)) {
3304
-            $sub_query_params = (array)$sub_query_params;
3305
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3306
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3307
-                $this->_extract_related_model_info_from_query_param(
3308
-                    $param,
3309
-                    $model_query_info_carrier,
3310
-                    $query_param_type
3311
-                );
3312
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3313
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3314
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3315
-                // of array('Registration.TXN_ID'=>23)
3316
-                $query_param_sans_stars =
3317
-                    $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3318
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3319
-                    if (! is_array($possibly_array_of_params)) {
3320
-                        throw new EE_Error(
3321
-                            sprintf(
3322
-                                esc_html__(
3323
-                                    "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'))",
3324
-                                    "event_espresso"
3325
-                                ),
3326
-                                $param,
3327
-                                $possibly_array_of_params
3328
-                            )
3329
-                        );
3330
-                    }
3331
-                    $this->_extract_related_models_from_sub_params_array_keys(
3332
-                        $possibly_array_of_params,
3333
-                        $model_query_info_carrier,
3334
-                        $query_param_type
3335
-                    );
3336
-                } elseif (
3337
-                    $query_param_type === 0 // ie WHERE
3338
-                    && is_array($possibly_array_of_params)
3339
-                    && isset($possibly_array_of_params[2])
3340
-                    && $possibly_array_of_params[2] == true
3341
-                ) {
3342
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3343
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3344
-                    // from which we should extract query parameters!
3345
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3346
-                        throw new EE_Error(
3347
-                            sprintf(
3348
-                                esc_html__(
3349
-                                    "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3350
-                                    "event_espresso"
3351
-                                ),
3352
-                                $query_param_type,
3353
-                                implode(",", $possibly_array_of_params)
3354
-                            )
3355
-                        );
3356
-                    }
3357
-                    $this->_extract_related_model_info_from_query_param(
3358
-                        $possibly_array_of_params[1],
3359
-                        $model_query_info_carrier,
3360
-                        $query_param_type
3361
-                    );
3362
-                }
3363
-            }
3364
-        }
3365
-        return $model_query_info_carrier;
3366
-    }
3367
-
3368
-
3369
-    /**
3370
-     * For extracting related models from forced_joins, where the array values contain the info about what
3371
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3372
-     *
3373
-     * @param array                       $sub_query_params
3374
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3375
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3376
-     * @return EE_Model_Query_Info_Carrier
3377
-     * @throws EE_Error
3378
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3379
-     */
3380
-    private function _extract_related_models_from_sub_params_array_values(
3381
-        $sub_query_params,
3382
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3383
-        $query_param_type
3384
-    ) {
3385
-        if (! empty($sub_query_params)) {
3386
-            if (! is_array($sub_query_params)) {
3387
-                throw new EE_Error(
3388
-                    sprintf(
3389
-                        esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3390
-                        $sub_query_params
3391
-                    )
3392
-                );
3393
-            }
3394
-            foreach ($sub_query_params as $param) {
3395
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3396
-                $this->_extract_related_model_info_from_query_param(
3397
-                    $param,
3398
-                    $model_query_info_carrier,
3399
-                    $query_param_type
3400
-                );
3401
-            }
3402
-        }
3403
-        return $model_query_info_carrier;
3404
-    }
3405
-
3406
-
3407
-    /**
3408
-     * Extract all the query parts from  model query params
3409
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3410
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3411
-     * but use them in a different order. Eg, we need to know what models we are querying
3412
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3413
-     * other models before we can finalize the where clause SQL.
3414
-     *
3415
-     * @param array $query_params see github link below for more info
3416
-     * @return EE_Model_Query_Info_Carrier
3417
-     * @throws EE_Error
3418
-     * @throws ModelConfigurationException
3419
-     * @throws ReflectionException
3420
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3421
-     */
3422
-    public function _create_model_query_info_carrier($query_params)
3423
-    {
3424
-        if (! is_array($query_params)) {
3425
-            EE_Error::doing_it_wrong(
3426
-                'EEM_Base::_create_model_query_info_carrier',
3427
-                sprintf(
3428
-                    esc_html__(
3429
-                        '$query_params should be an array, you passed a variable of type %s',
3430
-                        'event_espresso'
3431
-                    ),
3432
-                    gettype($query_params)
3433
-                ),
3434
-                '4.6.0'
3435
-            );
3436
-            $query_params = [];
3437
-        }
3438
-        $query_params[0] = isset($query_params[0]) ? $query_params[0] : [];
3439
-        // first check if we should alter the query to account for caps or not
3440
-        // because the caps might require us to do extra joins
3441
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3442
-            $query_params[0] = array_replace_recursive(
3443
-                $query_params[0],
3444
-                $this->caps_where_conditions(
3445
-                    $query_params['caps']
3446
-                )
3447
-            );
3448
-        }
3449
-
3450
-        // check if we should alter the query to remove data related to protected
3451
-        // custom post types
3452
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3453
-            $where_param_key_for_password = $this->modelChainAndPassword();
3454
-            // only include if related to a cpt where no password has been set
3455
-            $query_params[0]['OR*nopassword'] = [
3456
-                $where_param_key_for_password       => '',
3457
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3458
-            ];
3459
-        }
3460
-        $query_object = $this->_extract_related_models_from_query($query_params);
3461
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3462
-        foreach ($query_params[0] as $key => $value) {
3463
-            if (is_int($key)) {
3464
-                throw new EE_Error(
3465
-                    sprintf(
3466
-                        esc_html__(
3467
-                            "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.",
3468
-                            "event_espresso"
3469
-                        ),
3470
-                        $key,
3471
-                        var_export($value, true),
3472
-                        var_export($query_params, true),
3473
-                        get_class($this)
3474
-                    )
3475
-                );
3476
-            }
3477
-        }
3478
-        if (
3479
-            array_key_exists('default_where_conditions', $query_params)
3480
-            && ! empty($query_params['default_where_conditions'])
3481
-        ) {
3482
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3483
-        } else {
3484
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3485
-        }
3486
-        $query_params[0] = array_merge(
3487
-            $this->_get_default_where_conditions_for_models_in_query(
3488
-                $query_object,
3489
-                $use_default_where_conditions,
3490
-                $query_params[0]
3491
-            ),
3492
-            $query_params[0]
3493
-        );
3494
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3495
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3496
-        // So we need to setup a subquery and use that for the main join.
3497
-        // Note for now this only works on the primary table for the model.
3498
-        // So for instance, you could set the limit array like this:
3499
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3500
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3501
-            $query_object->set_main_model_join_sql(
3502
-                $this->_construct_limit_join_select(
3503
-                    $query_params['on_join_limit'][0],
3504
-                    $query_params['on_join_limit'][1]
3505
-                )
3506
-            );
3507
-        }
3508
-        // set limit
3509
-        if (array_key_exists('limit', $query_params)) {
3510
-            if (is_array($query_params['limit'])) {
3511
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3512
-                    $e = sprintf(
3513
-                        esc_html__(
3514
-                            "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)",
3515
-                            "event_espresso"
3516
-                        ),
3517
-                        http_build_query($query_params['limit'])
3518
-                    );
3519
-                    throw new EE_Error($e . "|" . $e);
3520
-                }
3521
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3522
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3523
-            } elseif (! empty($query_params['limit'])) {
3524
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3525
-            }
3526
-        }
3527
-        // set order by
3528
-        if (array_key_exists('order_by', $query_params)) {
3529
-            if (is_array($query_params['order_by'])) {
3530
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3531
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3532
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3533
-                if (array_key_exists('order', $query_params)) {
3534
-                    throw new EE_Error(
3535
-                        sprintf(
3536
-                            esc_html__(
3537
-                                "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 ",
3538
-                                "event_espresso"
3539
-                            ),
3540
-                            get_class($this),
3541
-                            implode(", ", array_keys($query_params['order_by'])),
3542
-                            implode(", ", $query_params['order_by']),
3543
-                            $query_params['order']
3544
-                        )
3545
-                    );
3546
-                }
3547
-                $this->_extract_related_models_from_sub_params_array_keys(
3548
-                    $query_params['order_by'],
3549
-                    $query_object,
3550
-                    'order_by'
3551
-                );
3552
-                // assume it's an array of fields to order by
3553
-                $order_array = [];
3554
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3555
-                    $order         = $this->_extract_order($order);
3556
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3557
-                }
3558
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3559
-            } elseif (! empty($query_params['order_by'])) {
3560
-                $this->_extract_related_model_info_from_query_param(
3561
-                    $query_params['order_by'],
3562
-                    $query_object,
3563
-                    'order',
3564
-                    $query_params['order_by']
3565
-                );
3566
-                $order = isset($query_params['order'])
3567
-                    ? $this->_extract_order($query_params['order'])
3568
-                    : 'DESC';
3569
-                $query_object->set_order_by_sql(
3570
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3571
-                );
3572
-            }
3573
-        }
3574
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3575
-        if (
3576
-            ! array_key_exists('order_by', $query_params)
3577
-            && array_key_exists('order', $query_params)
3578
-            && ! empty($query_params['order'])
3579
-        ) {
3580
-            $pk_field = $this->get_primary_key_field();
3581
-            $order    = $this->_extract_order($query_params['order']);
3582
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3583
-        }
3584
-        // set group by
3585
-        if (array_key_exists('group_by', $query_params)) {
3586
-            if (is_array($query_params['group_by'])) {
3587
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3588
-                $group_by_array = [];
3589
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3590
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3591
-                }
3592
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3593
-            } elseif (! empty($query_params['group_by'])) {
3594
-                $query_object->set_group_by_sql(
3595
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3596
-                );
3597
-            }
3598
-        }
3599
-        // set having
3600
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3601
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3602
-        }
3603
-        // now, just verify they didn't pass anything wack
3604
-        foreach ($query_params as $query_key => $query_value) {
3605
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3606
-                throw new EE_Error(
3607
-                    sprintf(
3608
-                        esc_html__(
3609
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3610
-                            'event_espresso'
3611
-                        ),
3612
-                        $query_key,
3613
-                        get_class($this),
3614
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3615
-                        implode(',', $this->_allowed_query_params)
3616
-                    )
3617
-                );
3618
-            }
3619
-        }
3620
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3621
-        if (empty($main_model_join_sql)) {
3622
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3623
-        }
3624
-        return $query_object;
3625
-    }
3626
-
3627
-
3628
-    /**
3629
-     * Gets the where conditions that should be imposed on the query based on the
3630
-     * context (eg reading frontend, backend, edit or delete).
3631
-     *
3632
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3633
-     * @return array
3634
-     * @throws EE_Error
3635
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3636
-     */
3637
-    public function caps_where_conditions($context = self::caps_read)
3638
-    {
3639
-        EEM_Base::verify_is_valid_cap_context($context);
3640
-        $cap_where_conditions = [];
3641
-        $cap_restrictions     = $this->caps_missing($context);
3642
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3643
-            $cap_where_conditions = array_replace_recursive(
3644
-                $cap_where_conditions,
3645
-                $restriction_if_no_cap->get_default_where_conditions()
3646
-            );
3647
-        }
3648
-        return apply_filters(
3649
-            'FHEE__EEM_Base__caps_where_conditions__return',
3650
-            $cap_where_conditions,
3651
-            $this,
3652
-            $context,
3653
-            $cap_restrictions
3654
-        );
3655
-    }
3656
-
3657
-
3658
-    /**
3659
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3660
-     * otherwise throws an exception
3661
-     *
3662
-     * @param string $should_be_order_string
3663
-     * @return string either ASC, asc, DESC or desc
3664
-     * @throws EE_Error
3665
-     */
3666
-    private function _extract_order($should_be_order_string)
3667
-    {
3668
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3669
-            return $should_be_order_string;
3670
-        }
3671
-        throw new EE_Error(
3672
-            sprintf(
3673
-                esc_html__(
3674
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3675
-                    "event_espresso"
3676
-                ),
3677
-                get_class($this),
3678
-                $should_be_order_string
3679
-            )
3680
-        );
3681
-    }
3682
-
3683
-
3684
-    /**
3685
-     * Looks at all the models which are included in this query, and asks each
3686
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3687
-     * so they can be merged
3688
-     *
3689
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3690
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3691
-     *                                                                  'none' means NO default where conditions will
3692
-     *                                                                  be used AT ALL during this query.
3693
-     *                                                                  'other_models_only' means default where
3694
-     *                                                                  conditions from other models will be used, but
3695
-     *                                                                  not for this primary model. 'all', the default,
3696
-     *                                                                  means default where conditions will apply as
3697
-     *                                                                  normal
3698
-     * @param array                       $where_query_params
3699
-     * @return array
3700
-     * @throws EE_Error
3701
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3702
-     */
3703
-    private function _get_default_where_conditions_for_models_in_query(
3704
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3705
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3706
-        $where_query_params = []
3707
-    ) {
3708
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3709
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3710
-            throw new EE_Error(
3711
-                sprintf(
3712
-                    esc_html__(
3713
-                        "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3714
-                        "event_espresso"
3715
-                    ),
3716
-                    $use_default_where_conditions,
3717
-                    implode(", ", $allowed_used_default_where_conditions_values)
3718
-                )
3719
-            );
3720
-        }
3721
-        $universal_query_params = [];
3722
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions)) {
3723
-            $universal_query_params = $this->_get_default_where_conditions();
3724
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions)) {
3725
-            $universal_query_params = $this->_get_minimum_where_conditions();
3726
-        }
3727
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3728
-            $related_model = $this->get_related_model_obj($model_name);
3729
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3730
-                $related_model_universal_where_params =
3731
-                    $related_model->_get_default_where_conditions($model_relation_path);
3732
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3733
-                $related_model_universal_where_params =
3734
-                    $related_model->_get_minimum_where_conditions($model_relation_path);
3735
-            } else {
3736
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3737
-                continue;
3738
-            }
3739
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3740
-                $related_model_universal_where_params,
3741
-                $where_query_params,
3742
-                $related_model,
3743
-                $model_relation_path
3744
-            );
3745
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3746
-                $universal_query_params,
3747
-                $overrides
3748
-            );
3749
-        }
3750
-        return $universal_query_params;
3751
-    }
3752
-
3753
-
3754
-    /**
3755
-     * Determines whether or not we should use default where conditions for the model in question
3756
-     * (this model, or other related models).
3757
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3758
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3759
-     * We should use default where conditions on related models when they requested to use default where conditions
3760
-     * on all models, or specifically just on other related models
3761
-     *
3762
-     * @param      $default_where_conditions_value
3763
-     * @param bool $for_this_model false means this is for OTHER related models
3764
-     * @return bool
3765
-     */
3766
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3767
-    {
3768
-        return (
3769
-                   $for_this_model
3770
-                   && in_array(
3771
-                       $default_where_conditions_value,
3772
-                       [
3773
-                           EEM_Base::default_where_conditions_all,
3774
-                           EEM_Base::default_where_conditions_this_only,
3775
-                           EEM_Base::default_where_conditions_minimum_others,
3776
-                       ],
3777
-                       true
3778
-                   )
3779
-               )
3780
-               || (
3781
-                   ! $for_this_model
3782
-                   && in_array(
3783
-                       $default_where_conditions_value,
3784
-                       [
3785
-                           EEM_Base::default_where_conditions_all,
3786
-                           EEM_Base::default_where_conditions_others_only,
3787
-                       ],
3788
-                       true
3789
-                   )
3790
-               );
3791
-    }
3792
-
3793
-
3794
-    /**
3795
-     * Determines whether or not we should use default minimum conditions for the model in question
3796
-     * (this model, or other related models).
3797
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3798
-     * where conditions.
3799
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3800
-     * on this model or others
3801
-     *
3802
-     * @param      $default_where_conditions_value
3803
-     * @param bool $for_this_model false means this is for OTHER related models
3804
-     * @return bool
3805
-     */
3806
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3807
-    {
3808
-        return (
3809
-                   $for_this_model
3810
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3811
-               )
3812
-               || (
3813
-                   ! $for_this_model
3814
-                   && in_array(
3815
-                       $default_where_conditions_value,
3816
-                       [
3817
-                           EEM_Base::default_where_conditions_minimum_others,
3818
-                           EEM_Base::default_where_conditions_minimum_all,
3819
-                       ],
3820
-                       true
3821
-                   )
3822
-               );
3823
-    }
3824
-
3825
-
3826
-    /**
3827
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3828
-     * then we also add a special where condition which allows for that model's primary key
3829
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3830
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3831
-     *
3832
-     * @param array    $default_where_conditions
3833
-     * @param array    $provided_where_conditions
3834
-     * @param EEM_Base $model
3835
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3836
-     * @return array
3837
-     * @throws EE_Error
3838
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3839
-     */
3840
-    private function _override_defaults_or_make_null_friendly(
3841
-        $default_where_conditions,
3842
-        $provided_where_conditions,
3843
-        $model,
3844
-        $model_relation_path
3845
-    ) {
3846
-        $null_friendly_where_conditions = [];
3847
-        $none_overridden                = true;
3848
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3849
-        foreach ($default_where_conditions as $key => $val) {
3850
-            if (isset($provided_where_conditions[ $key ])) {
3851
-                $none_overridden = false;
3852
-            } else {
3853
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3854
-            }
3855
-        }
3856
-        if ($none_overridden && $default_where_conditions) {
3857
-            if ($model->has_primary_key_field()) {
3858
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3859
-                                                                                   . "."
3860
-                                                                                   . $model->primary_key_name() ] =
3861
-                    ['IS NULL'];
3862
-            }/*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 null | string $timezone valid PHP DateTimeZone timezone string
1423
+	 */
1424
+	public function set_timezone($timezone)
1425
+	{
1426
+		if ($timezone !== null) {
1427
+			$this->_timezone = $timezone;
1428
+		}
1429
+		// note we need to loop through relations and set the timezone on those objects as well.
1430
+		foreach ($this->_model_relations as $relation) {
1431
+			$relation->set_timezone($timezone);
1432
+		}
1433
+		// and finally we do the same for any datetime fields
1434
+		foreach ($this->_fields as $field) {
1435
+			if ($field instanceof EE_Datetime_Field) {
1436
+				$field->set_timezone($timezone);
1437
+			}
1438
+		}
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * This just returns whatever is set for the current timezone.
1444
+	 *
1445
+	 * @access public
1446
+	 * @return string
1447
+	 */
1448
+	public function get_timezone()
1449
+	{
1450
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1451
+		if (empty($this->_timezone)) {
1452
+			foreach ($this->_fields as $field) {
1453
+				if ($field instanceof EE_Datetime_Field) {
1454
+					$this->set_timezone($field->get_timezone());
1455
+					break;
1456
+				}
1457
+			}
1458
+		}
1459
+		// if timezone STILL empty then return the default timezone for the site.
1460
+		if (empty($this->_timezone)) {
1461
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1462
+		}
1463
+		return $this->_timezone;
1464
+	}
1465
+
1466
+
1467
+	/**
1468
+	 * This returns the date formats set for the given field name and also ensures that
1469
+	 * $this->_timezone property is set correctly.
1470
+	 *
1471
+	 * @param string $field_name The name of the field the formats are being retrieved for.
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.
1474
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1475
+	 * @since 4.6.x
1476
+	 */
1477
+	public function get_formats_for($field_name, $pretty = false)
1478
+	{
1479
+		$field_settings = $this->field_settings_for($field_name);
1480
+		// if not a valid EE_Datetime_Field then throw error
1481
+		if (! $field_settings instanceof EE_Datetime_Field) {
1482
+			throw new EE_Error(
1483
+				sprintf(
1484
+					esc_html__(
1485
+						'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.',
1486
+						'event_espresso'
1487
+					),
1488
+					$field_name
1489
+				)
1490
+			);
1491
+		}
1492
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1493
+		// the field.
1494
+		$this->_timezone = $field_settings->get_timezone();
1495
+		return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1496
+	}
1497
+
1498
+
1499
+	/**
1500
+	 * This returns the current time in a format setup for a query on this model.
1501
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1502
+	 * it will return:
1503
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1504
+	 *  NOW
1505
+	 *  - or a unix timestamp (equivalent to time())
1506
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1507
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1508
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1509
+	 *
1510
+	 * @param string $field_name       The field the current time is needed for.
1511
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1512
+	 *                                 formatted string matching the set format for the field in the set timezone will
1513
+	 *                                 be returned.
1514
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1515
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1516
+	 *                                 exception is triggered.
1517
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1518
+	 * @throws Exception
1519
+	 * @since 4.6.x
1520
+	 */
1521
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1522
+	{
1523
+		$formats  = $this->get_formats_for($field_name);
1524
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1525
+		if ($timestamp) {
1526
+			return $DateTime->format('U');
1527
+		}
1528
+		// not returning timestamp, so return formatted string in timezone.
1529
+		switch ($what) {
1530
+			case 'time':
1531
+				return $DateTime->format($formats[1]);
1532
+			case 'date':
1533
+				return $DateTime->format($formats[0]);
1534
+			default:
1535
+				return $DateTime->format(implode(' ', $formats));
1536
+		}
1537
+	}
1538
+
1539
+
1540
+	/**
1541
+	 * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1542
+	 * for the model are.  Returns a DateTime object.
1543
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1544
+	 * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1545
+	 * ignored.
1546
+	 *
1547
+	 * @param string $field_name      The field being setup.
1548
+	 * @param string $timestring      The date time string being used.
1549
+	 * @param string $incoming_format The format for the time string.
1550
+	 * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1551
+	 *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1552
+	 *                                format is
1553
+	 *                                'U', this is ignored.
1554
+	 * @return DateTime
1555
+	 * @throws EE_Error
1556
+	 */
1557
+	public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1558
+	{
1559
+		// just using this to ensure the timezone is set correctly internally
1560
+		$this->get_formats_for($field_name);
1561
+		// load EEH_DTT_Helper
1562
+		$set_timezone     = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1563
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1564
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1565
+		return DbSafeDateTime::createFromDateTime($incomingDateTime);
1566
+	}
1567
+
1568
+
1569
+	/**
1570
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1571
+	 *
1572
+	 * @return EE_Table_Base[]
1573
+	 */
1574
+	public function get_tables()
1575
+	{
1576
+		return $this->_tables;
1577
+	}
1578
+
1579
+
1580
+	/**
1581
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1582
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1583
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1584
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1585
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1586
+	 * model object with EVT_ID = 1
1587
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1588
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1589
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1590
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1591
+	 * are not specified)
1592
+	 *
1593
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1594
+	 *                                         columns!), values are strings, integers, floats, and maybe arrays if
1595
+	 *                                         they
1596
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1597
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1598
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1599
+	 *                                         ID=34, we'd use this method as follows:
1600
+	 *                                         EEM_Transaction::instance()->update(
1601
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1602
+	 *                                         array(array('TXN_ID'=>34)));
1603
+	 * @param array   $query_params            Eg, consider updating Question's QST_admin_label field is of type
1604
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1605
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1606
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1607
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1608
+	 *                                         TRUE, it is assumed that you've already called
1609
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1610
+	 *                                         malicious javascript. However, if
1611
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1612
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1613
+	 *                                         and every other field, before insertion. We provide this parameter
1614
+	 *                                         because model objects perform their prepare_for_set function on all
1615
+	 *                                         their values, and so don't need to be called again (and in many cases,
1616
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1617
+	 *                                         prepare_for_set method...)
1618
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1619
+	 *                                         in this model's entity map according to $fields_n_values that match
1620
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1621
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1622
+	 *                                         could get out-of-sync with the database
1623
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1624
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1625
+	 *                                         bad)
1626
+	 * @throws EE_Error
1627
+	 * @throws ReflectionException
1628
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1629
+	 */
1630
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1631
+	{
1632
+		if (! is_array($query_params)) {
1633
+			EE_Error::doing_it_wrong(
1634
+				'EEM_Base::update',
1635
+				sprintf(
1636
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1637
+					gettype($query_params)
1638
+				),
1639
+				'4.6.0'
1640
+			);
1641
+			$query_params = [];
1642
+		}
1643
+		/**
1644
+		 * Action called before a model update call has been made.
1645
+		 *
1646
+		 * @param EEM_Base $model
1647
+		 * @param array    $fields_n_values the updated fields and their new values
1648
+		 * @param array    $query_params
1649
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1650
+		 */
1651
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1652
+		/**
1653
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1654
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1655
+		 *
1656
+		 * @param array    $fields_n_values fields and their new values
1657
+		 * @param EEM_Base $model           the model being queried
1658
+		 * @param array    $query_params
1659
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1660
+		 */
1661
+		$fields_n_values = (array)apply_filters(
1662
+			'FHEE__EEM_Base__update__fields_n_values',
1663
+			$fields_n_values,
1664
+			$this,
1665
+			$query_params
1666
+		);
1667
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1668
+		// to do that, for each table, verify that it's PK isn't null.
1669
+		$tables = $this->get_tables();
1670
+		// 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
1671
+		// NOTE: we should make this code more efficient by NOT querying twice
1672
+		// before the real update, but that needs to first go through ALPHA testing
1673
+		// as it's dangerous. says Mike August 8 2014
1674
+		// we want to make sure the default_where strategy is ignored
1675
+		$this->_ignore_where_strategy = true;
1676
+		$wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1677
+		foreach ($wpdb_select_results as $wpdb_result) {
1678
+			// type cast stdClass as array
1679
+			$wpdb_result = (array)$wpdb_result;
1680
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1681
+			if ($this->has_primary_key_field()) {
1682
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1683
+			} else {
1684
+				// 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)
1685
+				$main_table_pk_value = null;
1686
+			}
1687
+			// 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
1688
+			// 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
1689
+			if (count($tables) > 1) {
1690
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1691
+				// in that table, and so we'll want to insert one
1692
+				foreach ($tables as $table_obj) {
1693
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1694
+					// if there is no private key for this table on the results, it means there's no entry
1695
+					// in this table, right? so insert a row in the current table, using any fields available
1696
+					if (
1697
+					! (array_key_exists($this_table_pk_column, $wpdb_result)
1698
+					   && $wpdb_result[ $this_table_pk_column ])
1699
+					) {
1700
+						$success = $this->_insert_into_specific_table(
1701
+							$table_obj,
1702
+							$fields_n_values,
1703
+							$main_table_pk_value
1704
+						);
1705
+						// if we died here, report the error
1706
+						if (! $success) {
1707
+							return false;
1708
+						}
1709
+					}
1710
+				}
1711
+			}
1712
+			//              //and now check that if we have cached any models by that ID on the model, that
1713
+			//              //they also get updated properly
1714
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1715
+			//              if( $model_object ){
1716
+			//                  foreach( $fields_n_values as $field => $value ){
1717
+			//                      $model_object->set($field, $value);
1718
+			// let's make sure default_where strategy is followed now
1719
+			$this->_ignore_where_strategy = false;
1720
+		}
1721
+		// if we want to keep model objects in sync, AND
1722
+		// if this wasn't called from a model object (to update itself)
1723
+		// then we want to make sure we keep all the existing
1724
+		// model objects in sync with the db
1725
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1726
+			if ($this->has_primary_key_field()) {
1727
+				$model_objs_affected_ids = $this->get_col($query_params);
1728
+			} else {
1729
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1730
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params);
1731
+				$model_objs_affected_ids     = [];
1732
+				foreach ($models_affected_key_columns as $row) {
1733
+					$combined_index_key                             = $this->get_index_primary_key_string($row);
1734
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1735
+				}
1736
+			}
1737
+			if (! $model_objs_affected_ids) {
1738
+				// wait wait wait- if nothing was affected let's stop here
1739
+				return 0;
1740
+			}
1741
+			foreach ($model_objs_affected_ids as $id) {
1742
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1743
+				if ($model_obj_in_entity_map) {
1744
+					foreach ($fields_n_values as $field => $new_value) {
1745
+						$model_obj_in_entity_map->set($field, $new_value);
1746
+					}
1747
+				}
1748
+			}
1749
+			// if there is a primary key on this model, we can now do a slight optimization
1750
+			if ($this->has_primary_key_field()) {
1751
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1752
+				$query_params = [
1753
+					[$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1754
+					'limit'                    => count($model_objs_affected_ids),
1755
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1756
+				];
1757
+			}
1758
+		}
1759
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1760
+		$SQL              = "UPDATE "
1761
+							. $model_query_info->get_full_join_sql()
1762
+							. " SET "
1763
+							. $this->_construct_update_sql($fields_n_values)
1764
+							. $model_query_info->get_where_sql(
1765
+			);// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1766
+		$rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1767
+		/**
1768
+		 * Action called after a model update call has been made.
1769
+		 *
1770
+		 * @param EEM_Base $model
1771
+		 * @param array    $fields_n_values the updated fields and their new values
1772
+		 * @param array    $query_params
1773
+		 * @param int      $rows_affected
1774
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1775
+		 */
1776
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1777
+		return $rows_affected;// how many supposedly got updated
1778
+	}
1779
+
1780
+
1781
+	/**
1782
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1783
+	 * are teh values of the field specified (or by default the primary key field)
1784
+	 * that matched the query params. Note that you should pass the name of the
1785
+	 * model FIELD, not the database table's column name.
1786
+	 *
1787
+	 * @param array  $query_params
1788
+	 * @param string $field_to_select
1789
+	 * @return array just like $wpdb->get_col()
1790
+	 * @throws EE_Error
1791
+	 * @throws ReflectionException
1792
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1793
+	 */
1794
+	public function get_col($query_params = [], $field_to_select = null)
1795
+	{
1796
+		if ($field_to_select) {
1797
+			$field = $this->field_settings_for($field_to_select);
1798
+		} elseif ($this->has_primary_key_field()) {
1799
+			$field = $this->get_primary_key_field();
1800
+		} else {
1801
+			$field_settings = $this->field_settings();
1802
+			// no primary key, just grab the first column
1803
+			$field = reset($field_settings);
1804
+		}
1805
+		$model_query_info   = $this->_create_model_query_info_carrier($query_params);
1806
+		$select_expressions = $field->get_qualified_column();
1807
+		$SQL                =
1808
+			"SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1809
+		return $this->_do_wpdb_query('get_col', [$SQL]);
1810
+	}
1811
+
1812
+
1813
+	/**
1814
+	 * Returns a single column value for a single row from the database
1815
+	 *
1816
+	 * @param array  $query_params
1817
+	 * @param string $field_to_select
1818
+	 * @return string
1819
+	 * @throws EE_Error
1820
+	 * @throws ReflectionException
1821
+	 * @see EEM_Base::get_col()
1822
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1823
+	 */
1824
+	public function get_var($query_params = [], $field_to_select = null)
1825
+	{
1826
+		$query_params['limit'] = 1;
1827
+		$col                   = $this->get_col($query_params, $field_to_select);
1828
+		if (! empty($col)) {
1829
+			return reset($col);
1830
+		}
1831
+		return null;
1832
+	}
1833
+
1834
+
1835
+	/**
1836
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1837
+	 * time?', Question.desc='what do you think?'..." Values are filtered through wpdb->prepare to avoid against SQL
1838
+	 * injection, but currently no further filtering is done
1839
+	 *
1840
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1841
+	 *                               be updated to in the DB
1842
+	 * @return string of SQL
1843
+	 * @throws EE_Error
1844
+	 * @global      $wpdb
1845
+	 */
1846
+	public function _construct_update_sql($fields_n_values)
1847
+	{
1848
+		global $wpdb;
1849
+		$cols_n_values = [];
1850
+		foreach ($fields_n_values as $field_name => $value) {
1851
+			$field_obj = $this->field_settings_for($field_name);
1852
+			// if the value is NULL, we want to assign the value to that.
1853
+			// wpdb->prepare doesn't really handle that properly
1854
+			$prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1855
+			$value_sql       = $prepared_value === null ? 'NULL'
1856
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1857
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1858
+		}
1859
+		return implode(",", $cols_n_values);
1860
+	}
1861
+
1862
+
1863
+	/**
1864
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1865
+	 * Performs a HARD delete, meaning the database row should always be removed,
1866
+	 * not just have a flag field on it switched
1867
+	 * Wrapper for EEM_Base::delete_permanently()
1868
+	 *
1869
+	 * @param mixed   $id
1870
+	 * @param boolean $allow_blocking
1871
+	 * @return int the number of rows deleted
1872
+	 * @throws EE_Error
1873
+	 * @throws ReflectionException
1874
+	 */
1875
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1876
+	{
1877
+		return $this->delete_permanently(
1878
+			[
1879
+				[$this->get_primary_key_field()->get_name() => $id],
1880
+				'limit' => 1,
1881
+			],
1882
+			$allow_blocking
1883
+		);
1884
+	}
1885
+
1886
+
1887
+	/**
1888
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1889
+	 * Wrapper for EEM_Base::delete()
1890
+	 *
1891
+	 * @param mixed   $id
1892
+	 * @param boolean $allow_blocking
1893
+	 * @return int the number of rows deleted
1894
+	 * @throws EE_Error
1895
+	 * @throws ReflectionException
1896
+	 */
1897
+	public function delete_by_ID($id, $allow_blocking = true)
1898
+	{
1899
+		return $this->delete(
1900
+			[
1901
+				[$this->get_primary_key_field()->get_name() => $id],
1902
+				'limit' => 1,
1903
+			],
1904
+			$allow_blocking
1905
+		);
1906
+	}
1907
+
1908
+
1909
+	/**
1910
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1911
+	 * meaning if the model has a field that indicates its been "trashed" or
1912
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1913
+	 *
1914
+	 * @param array   $query_params
1915
+	 * @param boolean $allow_blocking
1916
+	 * @return int how many rows got deleted
1917
+	 * @throws EE_Error
1918
+	 * @throws ReflectionException
1919
+	 * @see EEM_Base::delete_permanently
1920
+	 */
1921
+	public function delete($query_params, $allow_blocking = true)
1922
+	{
1923
+		return $this->delete_permanently($query_params, $allow_blocking);
1924
+	}
1925
+
1926
+
1927
+	/**
1928
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
1929
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1930
+	 * as archived, not actually deleted
1931
+	 *
1932
+	 * @param array   $query_params
1933
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1934
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1935
+	 *                                deletes regardless of other objects which may depend on it. Its generally
1936
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1937
+	 *                                DB
1938
+	 * @return int how many rows got deleted
1939
+	 * @throws EE_Error
1940
+	 * @throws ReflectionException
1941
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1942
+	 */
1943
+	public function delete_permanently($query_params, $allow_blocking = true)
1944
+	{
1945
+		/**
1946
+		 * Action called just before performing a real deletion query. You can use the
1947
+		 * model and its $query_params to find exactly which items will be deleted
1948
+		 *
1949
+		 * @param EEM_Base $model
1950
+		 * @param array    $query_params
1951
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
1952
+		 *                                 to block (prevent) this deletion
1953
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1954
+		 */
1955
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1956
+		// some MySQL databases may be running safe mode, which may restrict
1957
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
1958
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
1959
+		// to delete them
1960
+		$items_for_deletion           = $this->_get_all_wpdb_results($query_params);
1961
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1962
+		$deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
1963
+			$columns_and_ids_for_deleting
1964
+		);
1965
+		/**
1966
+		 * Allows client code to act on the items being deleted before the query is actually executed.
1967
+		 *
1968
+		 * @param EEM_Base $this                            The model instance being acted on.
1969
+		 * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
1970
+		 * @param bool     $allow_blocking                  @see param description in method phpdoc block.
1971
+		 * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
1972
+		 *                                                  derived from the incoming query parameters.
1973
+		 * @see details on the structure of this array in the phpdocs
1974
+		 *                                                  for the `_get_ids_for_delete_method`
1975
+		 *
1976
+		 */
1977
+		do_action(
1978
+			'AHEE__EEM_Base__delete__before_query',
1979
+			$this,
1980
+			$query_params,
1981
+			$allow_blocking,
1982
+			$columns_and_ids_for_deleting
1983
+		);
1984
+		if ($deletion_where_query_part) {
1985
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
1986
+			$table_aliases    = array_keys($this->_tables);
1987
+			$SQL              = "DELETE "
1988
+								. implode(", ", $table_aliases)
1989
+								. " FROM "
1990
+								. $model_query_info->get_full_join_sql()
1991
+								. " WHERE "
1992
+								. $deletion_where_query_part;
1993
+			$rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
1994
+		} else {
1995
+			$rows_deleted = 0;
1996
+		}
1997
+
1998
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
1999
+		// there was no error with the delete query.
2000
+		if (
2001
+			$this->has_primary_key_field()
2002
+			&& $rows_deleted !== false
2003
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2004
+		) {
2005
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2006
+			foreach ($ids_for_removal as $id) {
2007
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2008
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2009
+				}
2010
+			}
2011
+
2012
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2013
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2014
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2015
+			// (although it is possible).
2016
+			// Note this can be skipped by using the provided filter and returning false.
2017
+			if (
2018
+			apply_filters(
2019
+				'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2020
+				! $this instanceof EEM_Extra_Meta,
2021
+				$this
2022
+			)
2023
+			) {
2024
+				EEM_Extra_Meta::instance()->delete_permanently(
2025
+					[
2026
+						0 => [
2027
+							'EXM_type' => $this->get_this_model_name(),
2028
+							'OBJ_ID'   => [
2029
+								'IN',
2030
+								$ids_for_removal,
2031
+							],
2032
+						],
2033
+					]
2034
+				);
2035
+			}
2036
+		}
2037
+
2038
+		/**
2039
+		 * Action called just after performing a real deletion query. Although at this point the
2040
+		 * items should have been deleted
2041
+		 *
2042
+		 * @param EEM_Base $model
2043
+		 * @param array    $query_params
2044
+		 * @param int      $rows_deleted
2045
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2046
+		 */
2047
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2048
+		return $rows_deleted;// how many supposedly got deleted
2049
+	}
2050
+
2051
+
2052
+	/**
2053
+	 * Checks all the relations that throw error messages when there are blocking related objects
2054
+	 * for related model objects. If there are any related model objects on those relations,
2055
+	 * adds an EE_Error, and return true
2056
+	 *
2057
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2058
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2059
+	 *                                                 should be ignored when determining whether there are related
2060
+	 *                                                 model objects which block this model object's deletion. Useful
2061
+	 *                                                 if you know A is related to B and are considering deleting A,
2062
+	 *                                                 but want to see if A has any other objects blocking its deletion
2063
+	 *                                                 before removing the relation between A and B
2064
+	 * @return boolean
2065
+	 * @throws EE_Error
2066
+	 * @throws ReflectionException
2067
+	 */
2068
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2069
+	{
2070
+		// first, if $ignore_this_model_obj was supplied, get its model
2071
+		if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2072
+			$ignored_model = $ignore_this_model_obj->get_model();
2073
+		} else {
2074
+			$ignored_model = null;
2075
+		}
2076
+		// now check all the relations of $this_model_obj_or_id and see if there
2077
+		// are any related model objects blocking it?
2078
+		$is_blocked = false;
2079
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2080
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2081
+				// if $ignore_this_model_obj was supplied, then for the query
2082
+				// on that model needs to be told to ignore $ignore_this_model_obj
2083
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2084
+					$related_model_objects = $relation_obj->get_all_related(
2085
+						$this_model_obj_or_id,
2086
+						[
2087
+							[
2088
+								$ignored_model->get_primary_key_field()->get_name() => [
2089
+									'!=',
2090
+									$ignore_this_model_obj->ID(),
2091
+								],
2092
+							],
2093
+						]
2094
+					);
2095
+				} else {
2096
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2097
+				}
2098
+				if ($related_model_objects) {
2099
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2100
+					$is_blocked = true;
2101
+				}
2102
+			}
2103
+		}
2104
+		return $is_blocked;
2105
+	}
2106
+
2107
+
2108
+	/**
2109
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2110
+	 *
2111
+	 * @param array $row_results_for_deleting
2112
+	 * @param bool  $allow_blocking
2113
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2114
+	 *                              model DOES have a primary_key_field, then the array will be a simple single
2115
+	 *                              dimension array where the key is the fully qualified primary key column and the
2116
+	 *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2117
+	 *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2118
+	 *                              be a two dimensional array where each element is a group of columns and values that
2119
+	 *                              get deleted. Example: array(
2120
+	 *                              0 => array(
2121
+	 *                              'Term_Relationship.object_id' => 1
2122
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2123
+	 *                              ),
2124
+	 *                              1 => array(
2125
+	 *                              'Term_Relationship.object_id' => 1
2126
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2127
+	 *                              )
2128
+	 *                              )
2129
+	 * @throws EE_Error
2130
+	 * @throws ReflectionException
2131
+	 */
2132
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2133
+	{
2134
+		$ids_to_delete_indexed_by_column = [];
2135
+		if ($this->has_primary_key_field()) {
2136
+			$primary_table                   = $this->_get_main_table();
2137
+			$ids_to_delete_indexed_by_column = $query = [];
2138
+			foreach ($row_results_for_deleting as $item_to_delete) {
2139
+				// before we mark this item for deletion,
2140
+				// make sure there's no related entities blocking its deletion (if we're checking)
2141
+				if (
2142
+					$allow_blocking
2143
+					&& $this->delete_is_blocked_by_related_models(
2144
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2145
+					)
2146
+				) {
2147
+					continue;
2148
+				}
2149
+				// primary table deletes
2150
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2151
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2152
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2153
+				}
2154
+			}
2155
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2156
+			$fields = $this->get_combined_primary_key_fields();
2157
+			foreach ($row_results_for_deleting as $item_to_delete) {
2158
+				$ids_to_delete_indexed_by_column_for_row = [];
2159
+				foreach ($fields as $cpk_field) {
2160
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2161
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2162
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2163
+					}
2164
+				}
2165
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2166
+			}
2167
+		} else {
2168
+			// so there's no primary key and no combined key...
2169
+			// sorry, can't help you
2170
+			throw new EE_Error(
2171
+				sprintf(
2172
+					esc_html__(
2173
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2174
+						"event_espresso"
2175
+					),
2176
+					get_class($this)
2177
+				)
2178
+			);
2179
+		}
2180
+		return $ids_to_delete_indexed_by_column;
2181
+	}
2182
+
2183
+
2184
+	/**
2185
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2186
+	 * the corresponding query_part for the query performing the delete.
2187
+	 *
2188
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2189
+	 * @return string
2190
+	 * @throws EE_Error
2191
+	 */
2192
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2193
+	{
2194
+		$query_part = '';
2195
+		if (empty($ids_to_delete_indexed_by_column)) {
2196
+			return $query_part;
2197
+		} elseif ($this->has_primary_key_field()) {
2198
+			$query = [];
2199
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2200
+				// make sure we have unique $ids
2201
+				$ids     = array_unique($ids);
2202
+				$query[] = $column . ' IN(' . implode(',', $ids) . ')';
2203
+			}
2204
+			$query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2205
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2206
+			$ways_to_identify_a_row = [];
2207
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2208
+				$values_for_each_combined_primary_key_for_a_row = [];
2209
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2210
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2211
+				}
2212
+				$ways_to_identify_a_row[] = '('
2213
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2214
+											. ')';
2215
+			}
2216
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2217
+		}
2218
+		return $query_part;
2219
+	}
2220
+
2221
+
2222
+	/**
2223
+	 * Gets the model field by the fully qualified name
2224
+	 *
2225
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2226
+	 * @return EE_Model_Field_Base
2227
+	 * @throws EE_Error
2228
+	 */
2229
+	public function get_field_by_column($qualified_column_name)
2230
+	{
2231
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2232
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2233
+				return $field_obj;
2234
+			}
2235
+		}
2236
+		throw new EE_Error(
2237
+			sprintf(
2238
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2239
+				$this->get_this_model_name(),
2240
+				$qualified_column_name
2241
+			)
2242
+		);
2243
+	}
2244
+
2245
+
2246
+	/**
2247
+	 * Count all the rows that match criteria the model query params.
2248
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2249
+	 * column
2250
+	 *
2251
+	 * @param array  $query_params
2252
+	 * @param string $field_to_count field on model to count by (not column name)
2253
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2254
+	 *                               that by the setting $distinct to TRUE;
2255
+	 * @return int
2256
+	 * @throws EE_Error
2257
+	 * @throws ReflectionException
2258
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2259
+	 */
2260
+	public function count($query_params = [], $field_to_count = null, $distinct = false)
2261
+	{
2262
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2263
+		if ($field_to_count) {
2264
+			$field_obj       = $this->field_settings_for($field_to_count);
2265
+			$column_to_count = $field_obj->get_qualified_column();
2266
+		} elseif ($this->has_primary_key_field()) {
2267
+			$pk_field_obj    = $this->get_primary_key_field();
2268
+			$column_to_count = $pk_field_obj->get_qualified_column();
2269
+		} else {
2270
+			// there's no primary key
2271
+			// if we're counting distinct items, and there's no primary key,
2272
+			// we need to list out the columns for distinction;
2273
+			// otherwise we can just use star
2274
+			if ($distinct) {
2275
+				$columns_to_use = [];
2276
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2277
+					$columns_to_use[] = $field_obj->get_qualified_column();
2278
+				}
2279
+				$column_to_count = implode(',', $columns_to_use);
2280
+			} else {
2281
+				$column_to_count = '*';
2282
+			}
2283
+		}
2284
+		$column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2285
+		$SQL             =
2286
+			"SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2287
+		return (int)$this->_do_wpdb_query('get_var', [$SQL]);
2288
+	}
2289
+
2290
+
2291
+	/**
2292
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2293
+	 *
2294
+	 * @param array  $query_params
2295
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2296
+	 * @return float
2297
+	 * @throws EE_Error
2298
+	 * @throws ReflectionException
2299
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2300
+	 */
2301
+	public function sum($query_params, $field_to_sum = null)
2302
+	{
2303
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2304
+		if ($field_to_sum) {
2305
+			$field_obj = $this->field_settings_for($field_to_sum);
2306
+		} else {
2307
+			$field_obj = $this->get_primary_key_field();
2308
+		}
2309
+		$column_to_count = $field_obj->get_qualified_column();
2310
+		$SQL             =
2311
+			"SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2312
+		$return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2313
+		$data_type       = $field_obj->get_wpdb_data_type();
2314
+		if ($data_type === '%d' || $data_type === '%s') {
2315
+			return (float)$return_value;
2316
+		}
2317
+		// must be %f
2318
+		return (float)$return_value;
2319
+	}
2320
+
2321
+
2322
+	/**
2323
+	 * Just calls the specified method on $wpdb with the given arguments
2324
+	 * Consolidates a little extra error handling code
2325
+	 *
2326
+	 * @param string $wpdb_method
2327
+	 * @param array  $arguments_to_provide
2328
+	 * @return mixed
2329
+	 * @throws EE_Error
2330
+	 * @global wpdb  $wpdb
2331
+	 */
2332
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2333
+	{
2334
+		// if we're in maintenance mode level 2, DON'T run any queries
2335
+		// because level 2 indicates the database needs updating and
2336
+		// is probably out of sync with the code
2337
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2338
+			throw new EE_Error(
2339
+				sprintf(
2340
+					esc_html__(
2341
+						"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.",
2342
+						"event_espresso"
2343
+					)
2344
+				)
2345
+			);
2346
+		}
2347
+		global $wpdb;
2348
+		if (! method_exists($wpdb, $wpdb_method)) {
2349
+			throw new EE_Error(
2350
+				sprintf(
2351
+					esc_html__(
2352
+						'There is no method named "%s" on Wordpress\' $wpdb object',
2353
+						'event_espresso'
2354
+					),
2355
+					$wpdb_method
2356
+				)
2357
+			);
2358
+		}
2359
+		$old_show_errors_value = $wpdb->show_errors;
2360
+		if (WP_DEBUG) {
2361
+			$wpdb->show_errors(false);
2362
+		}
2363
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2364
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2365
+		if (WP_DEBUG) {
2366
+			$wpdb->show_errors($old_show_errors_value);
2367
+			if (! empty($wpdb->last_error)) {
2368
+				throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2369
+			}
2370
+			if ($result === false) {
2371
+				throw new EE_Error(
2372
+					sprintf(
2373
+						esc_html__(
2374
+							'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2375
+							'event_espresso'
2376
+						),
2377
+						$wpdb_method,
2378
+						var_export($arguments_to_provide, true)
2379
+					)
2380
+				);
2381
+			}
2382
+		} elseif ($result === false) {
2383
+			EE_Error::add_error(
2384
+				sprintf(
2385
+					esc_html__(
2386
+						'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"',
2387
+						'event_espresso'
2388
+					),
2389
+					$wpdb_method,
2390
+					var_export($arguments_to_provide, true),
2391
+					$wpdb->last_error
2392
+				),
2393
+				__FILE__,
2394
+				__FUNCTION__,
2395
+				__LINE__
2396
+			);
2397
+		}
2398
+		return $result;
2399
+	}
2400
+
2401
+
2402
+	/**
2403
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2404
+	 * and if there's an error tries to verify the DB is correct. Uses
2405
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2406
+	 * we should try to fix the EE core db, the addons, or just give up
2407
+	 *
2408
+	 * @param string $wpdb_method
2409
+	 * @param array  $arguments_to_provide
2410
+	 * @return mixed
2411
+	 * @throws EE_Error
2412
+	 */
2413
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2414
+	{
2415
+		global $wpdb;
2416
+		$wpdb->last_error = null;
2417
+		$result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2418
+		// was there an error running the query? but we don't care on new activations
2419
+		// (we're going to setup the DB anyway on new activations)
2420
+		if (
2421
+			($result === false || ! empty($wpdb->last_error))
2422
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2423
+		) {
2424
+			switch (EEM_Base::$_db_verification_level) {
2425
+				case EEM_Base::db_verified_none:
2426
+					// let's double-check core's DB
2427
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2428
+					break;
2429
+				case EEM_Base::db_verified_core:
2430
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2431
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2432
+					break;
2433
+				case EEM_Base::db_verified_addons:
2434
+					// ummmm... you in trouble
2435
+					return $result;
2436
+			}
2437
+			if (! empty($error_message)) {
2438
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2439
+				trigger_error($error_message);
2440
+			}
2441
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2442
+		}
2443
+		return $result;
2444
+	}
2445
+
2446
+
2447
+	/**
2448
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2449
+	 * EEM_Base::$_db_verification_level
2450
+	 *
2451
+	 * @param string $wpdb_method
2452
+	 * @param array  $arguments_to_provide
2453
+	 * @return string
2454
+	 * @throws EE_Error
2455
+	 */
2456
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2457
+	{
2458
+		global $wpdb;
2459
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2460
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2461
+		$error_message                    = sprintf(
2462
+			esc_html__(
2463
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2464
+				'event_espresso'
2465
+			),
2466
+			$wpdb->last_error,
2467
+			$wpdb_method,
2468
+			wp_json_encode($arguments_to_provide)
2469
+		);
2470
+		EE_System::instance()->initialize_db_if_no_migrations_required();
2471
+		return $error_message;
2472
+	}
2473
+
2474
+
2475
+	/**
2476
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2477
+	 * EEM_Base::$_db_verification_level
2478
+	 *
2479
+	 * @param $wpdb_method
2480
+	 * @param $arguments_to_provide
2481
+	 * @return string
2482
+	 * @throws EE_Error
2483
+	 */
2484
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2485
+	{
2486
+		global $wpdb;
2487
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2488
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2489
+		$error_message                    = sprintf(
2490
+			esc_html__(
2491
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2492
+				'event_espresso'
2493
+			),
2494
+			$wpdb->last_error,
2495
+			$wpdb_method,
2496
+			wp_json_encode($arguments_to_provide)
2497
+		);
2498
+		EE_System::instance()->initialize_addons();
2499
+		return $error_message;
2500
+	}
2501
+
2502
+
2503
+	/**
2504
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2505
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2506
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2507
+	 * ..."
2508
+	 *
2509
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2510
+	 * @return string
2511
+	 */
2512
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2513
+	{
2514
+		return " FROM " . $model_query_info->get_full_join_sql() .
2515
+			   $model_query_info->get_where_sql() .
2516
+			   $model_query_info->get_group_by_sql() .
2517
+			   $model_query_info->get_having_sql() .
2518
+			   $model_query_info->get_order_by_sql() .
2519
+			   $model_query_info->get_limit_sql();
2520
+	}
2521
+
2522
+
2523
+	/**
2524
+	 * Set to easily debug the next X queries ran from this model.
2525
+	 *
2526
+	 * @param int $count
2527
+	 */
2528
+	public function show_next_x_db_queries($count = 1)
2529
+	{
2530
+		$this->_show_next_x_db_queries = $count;
2531
+	}
2532
+
2533
+
2534
+	/**
2535
+	 * @param $sql_query
2536
+	 */
2537
+	public function show_db_query_if_previously_requested($sql_query)
2538
+	{
2539
+		if ($this->_show_next_x_db_queries > 0) {
2540
+			echo $sql_query;
2541
+			$this->_show_next_x_db_queries--;
2542
+		}
2543
+	}
2544
+
2545
+
2546
+	/**
2547
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2548
+	 * There are the 3 cases:
2549
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2550
+	 * $otherModelObject has no ID, it is first saved.
2551
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2552
+	 * has no ID, it is first saved.
2553
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2554
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2555
+	 * join table
2556
+	 *
2557
+	 * @param EE_Base_Class                     /int $thisModelObject
2558
+	 * @param EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2559
+	 * @param string $relationName                     , key in EEM_Base::_relations
2560
+	 *                                                 an attendee to a group, you also want to specify which role they
2561
+	 *                                                 will have in that group. So you would use this parameter to
2562
+	 *                                                 specify array('role-column-name'=>'role-id')
2563
+	 * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2564
+	 *                                                 to for relation to methods that allow you to further specify
2565
+	 *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2566
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2567
+	 *                                                 because these will be inserted in any new rows created as well.
2568
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2569
+	 * @throws EE_Error
2570
+	 */
2571
+	public function add_relationship_to(
2572
+		$id_or_obj,
2573
+		$other_model_id_or_obj,
2574
+		$relationName,
2575
+		$extra_join_model_fields_n_values = []
2576
+	) {
2577
+		$relation_obj = $this->related_settings_for($relationName);
2578
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2579
+	}
2580
+
2581
+
2582
+	/**
2583
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2584
+	 * There are the 3 cases:
2585
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2586
+	 * error
2587
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2588
+	 * an error
2589
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2590
+	 *
2591
+	 * @param EE_Base_Class /int $id_or_obj
2592
+	 * @param EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2593
+	 * @param string $relationName key in EEM_Base::_relations
2594
+	 * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2595
+	 *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2596
+	 *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2597
+	 *                             because these will be inserted in any new rows created as well.
2598
+	 * @return boolean of success
2599
+	 * @throws EE_Error
2600
+	 */
2601
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2602
+	{
2603
+		$relation_obj = $this->related_settings_for($relationName);
2604
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2605
+	}
2606
+
2607
+
2608
+	/**
2609
+	 * @param mixed  $id_or_obj
2610
+	 * @param string $relationName
2611
+	 * @param array  $where_query_params
2612
+	 * @param EE_Base_Class[] objects to which relations were removed
2613
+	 * @return EE_Base_Class[]
2614
+	 * @throws EE_Error
2615
+	 */
2616
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2617
+	{
2618
+		$relation_obj = $this->related_settings_for($relationName);
2619
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2620
+	}
2621
+
2622
+
2623
+	/**
2624
+	 * Gets all the related items of the specified $model_name, using $query_params.
2625
+	 * Note: by default, we remove the "default query params"
2626
+	 * because we want to get even deleted items etc.
2627
+	 *
2628
+	 * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2629
+	 * @param string $model_name   like 'Event', 'Registration', etc. always singular
2630
+	 * @param array  $query_params see github link below for more info
2631
+	 * @return EE_Base_Class[]
2632
+	 * @throws EE_Error
2633
+	 * @throws ReflectionException
2634
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2635
+	 */
2636
+	public function get_all_related($id_or_obj, $model_name, $query_params = null)
2637
+	{
2638
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2639
+		$relation_settings = $this->related_settings_for($model_name);
2640
+		return $relation_settings->get_all_related($model_obj, $query_params);
2641
+	}
2642
+
2643
+
2644
+	/**
2645
+	 * Deletes all the model objects across the relation indicated by $model_name
2646
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2647
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2648
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2649
+	 *
2650
+	 * @param EE_Base_Class|int|string $id_or_obj
2651
+	 * @param string                   $model_name
2652
+	 * @param array                    $query_params
2653
+	 * @return int how many deleted
2654
+	 * @throws EE_Error
2655
+	 * @throws ReflectionException
2656
+	 */
2657
+	public function delete_related($id_or_obj, $model_name, $query_params = [])
2658
+	{
2659
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2660
+		$relation_settings = $this->related_settings_for($model_name);
2661
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2662
+	}
2663
+
2664
+
2665
+	/**
2666
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2667
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2668
+	 * the model objects can't be hard deleted because of blocking related model objects,
2669
+	 * just does a soft-delete on them instead.
2670
+	 *
2671
+	 * @param EE_Base_Class|int|string $id_or_obj
2672
+	 * @param string                   $model_name
2673
+	 * @param array                    $query_params
2674
+	 * @return int how many deleted
2675
+	 * @throws EE_Error
2676
+	 * @throws ReflectionException
2677
+	 */
2678
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2679
+	{
2680
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2681
+		$relation_settings = $this->related_settings_for($model_name);
2682
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2683
+	}
2684
+
2685
+
2686
+	/**
2687
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2688
+	 * unless otherwise specified in the $query_params
2689
+	 *
2690
+	 * @param int             /EE_Base_Class $id_or_obj
2691
+	 * @param string $model_name     like 'Event', or 'Registration'
2692
+	 * @param array  $query_params
2693
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2694
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2695
+	 *                               that by the setting $distinct to TRUE;
2696
+	 * @return int
2697
+	 * @throws EE_Error
2698
+	 * @throws ReflectionException
2699
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2700
+	 */
2701
+	public function count_related(
2702
+		$id_or_obj,
2703
+		$model_name,
2704
+		$query_params = [],
2705
+		$field_to_count = null,
2706
+		$distinct = false
2707
+	) {
2708
+		$related_model = $this->get_related_model_obj($model_name);
2709
+		// we're just going to use the query params on the related model's normal get_all query,
2710
+		// except add a condition to say to match the current mod
2711
+		if (! isset($query_params['default_where_conditions'])) {
2712
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2713
+		}
2714
+		$this_model_name                                                 = $this->get_this_model_name();
2715
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2716
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2717
+		return $related_model->count($query_params, $field_to_count, $distinct);
2718
+	}
2719
+
2720
+
2721
+	/**
2722
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2723
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2724
+	 *
2725
+	 * @param int           /EE_Base_Class $id_or_obj
2726
+	 * @param string $model_name   like 'Event', or 'Registration'
2727
+	 * @param array  $query_params
2728
+	 * @param string $field_to_sum name of field to count by. By default, uses primary key
2729
+	 * @return float
2730
+	 * @throws EE_Error
2731
+	 * @throws ReflectionException
2732
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2733
+	 */
2734
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2735
+	{
2736
+		$related_model = $this->get_related_model_obj($model_name);
2737
+		if (! is_array($query_params)) {
2738
+			EE_Error::doing_it_wrong(
2739
+				'EEM_Base::sum_related',
2740
+				sprintf(
2741
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2742
+					gettype($query_params)
2743
+				),
2744
+				'4.6.0'
2745
+			);
2746
+			$query_params = [];
2747
+		}
2748
+		// we're just going to use the query params on the related model's normal get_all query,
2749
+		// except add a condition to say to match the current mod
2750
+		if (! isset($query_params['default_where_conditions'])) {
2751
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2752
+		}
2753
+		$this_model_name                                                 = $this->get_this_model_name();
2754
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2755
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2756
+		return $related_model->sum($query_params, $field_to_sum);
2757
+	}
2758
+
2759
+
2760
+	/**
2761
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2762
+	 * $modelObject
2763
+	 *
2764
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2765
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2766
+	 * @param array               $query_params     see github link below for more info
2767
+	 * @return EE_Base_Class
2768
+	 * @throws EE_Error
2769
+	 * @throws ReflectionException
2770
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2771
+	 */
2772
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2773
+	{
2774
+		$query_params['limit'] = 1;
2775
+		$results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2776
+		if ($results) {
2777
+			return array_shift($results);
2778
+		}
2779
+		return null;
2780
+	}
2781
+
2782
+
2783
+	/**
2784
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2785
+	 *
2786
+	 * @return string
2787
+	 */
2788
+	public function get_this_model_name()
2789
+	{
2790
+		return str_replace("EEM_", "", get_class($this));
2791
+	}
2792
+
2793
+
2794
+	/**
2795
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2796
+	 *
2797
+	 * @return EE_Any_Foreign_Model_Name_Field
2798
+	 * @throws EE_Error
2799
+	 */
2800
+	public function get_field_containing_related_model_name()
2801
+	{
2802
+		foreach ($this->field_settings(true) as $field) {
2803
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2804
+				$field_with_model_name = $field;
2805
+			}
2806
+		}
2807
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2808
+			throw new EE_Error(
2809
+				sprintf(
2810
+					esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2811
+					$this->get_this_model_name()
2812
+				)
2813
+			);
2814
+		}
2815
+		return $field_with_model_name;
2816
+	}
2817
+
2818
+
2819
+	/**
2820
+	 * Inserts a new entry into the database, for each table.
2821
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2822
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2823
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2824
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2825
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2826
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2827
+	 *
2828
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2829
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2830
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2831
+	 *                              of EEM_Base)
2832
+	 * @return int|string new primary key on main table that got inserted
2833
+	 * @throws EE_Error
2834
+	 * @throws ReflectionException
2835
+	 */
2836
+	public function insert($field_n_values)
2837
+	{
2838
+		/**
2839
+		 * Filters the fields and their values before inserting an item using the models
2840
+		 *
2841
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2842
+		 * @param EEM_Base $model           the model used
2843
+		 */
2844
+		$field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2845
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2846
+			$main_table = $this->_get_main_table();
2847
+			$new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2848
+			if ($new_id !== false) {
2849
+				foreach ($this->_get_other_tables() as $other_table) {
2850
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2851
+				}
2852
+			}
2853
+			/**
2854
+			 * Done just after attempting to insert a new model object
2855
+			 *
2856
+			 * @param EEM_Base $model           used
2857
+			 * @param array    $fields_n_values fields and their values
2858
+			 * @param int|string the              ID of the newly-inserted model object
2859
+			 */
2860
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2861
+			return $new_id;
2862
+		}
2863
+		return false;
2864
+	}
2865
+
2866
+
2867
+	/**
2868
+	 * Checks that the result would satisfy the unique indexes on this model
2869
+	 *
2870
+	 * @param array  $field_n_values
2871
+	 * @param string $action
2872
+	 * @return boolean
2873
+	 * @throws EE_Error
2874
+	 * @throws ReflectionException
2875
+	 */
2876
+	protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2877
+	{
2878
+		foreach ($this->unique_indexes() as $index_name => $index) {
2879
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2880
+			if ($this->exists([$uniqueness_where_params])) {
2881
+				EE_Error::add_error(
2882
+					sprintf(
2883
+						esc_html__(
2884
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2885
+							"event_espresso"
2886
+						),
2887
+						$action,
2888
+						$this->_get_class_name(),
2889
+						$index_name,
2890
+						implode(",", $index->field_names()),
2891
+						http_build_query($uniqueness_where_params)
2892
+					),
2893
+					__FILE__,
2894
+					__FUNCTION__,
2895
+					__LINE__
2896
+				);
2897
+				return false;
2898
+			}
2899
+		}
2900
+		return true;
2901
+	}
2902
+
2903
+
2904
+	/**
2905
+	 * Checks the database for an item that conflicts (ie, if this item were
2906
+	 * saved to the DB would break some uniqueness requirement, like a primary key
2907
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
2908
+	 * can be either an EE_Base_Class or an array of fields n values
2909
+	 *
2910
+	 * @param EE_Base_Class|array $obj_or_fields_array
2911
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
2912
+	 *                                                 when looking for conflicts
2913
+	 *                                                 (ie, if false, we ignore the model object's primary key
2914
+	 *                                                 when finding "conflicts". If true, it's also considered).
2915
+	 *                                                 Only works for INT primary key,
2916
+	 *                                                 STRING primary keys cannot be ignored
2917
+	 * @return EE_Base_Class|array
2918
+	 * @throws EE_Error
2919
+	 * @throws ReflectionException
2920
+	 */
2921
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2922
+	{
2923
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
2924
+			$fields_n_values = $obj_or_fields_array->model_field_array();
2925
+		} elseif (is_array($obj_or_fields_array)) {
2926
+			$fields_n_values = $obj_or_fields_array;
2927
+		} else {
2928
+			throw new EE_Error(
2929
+				sprintf(
2930
+					esc_html__(
2931
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2932
+						"event_espresso"
2933
+					),
2934
+					get_class($this),
2935
+					$obj_or_fields_array
2936
+				)
2937
+			);
2938
+		}
2939
+		$query_params = [];
2940
+		if (
2941
+			$this->has_primary_key_field()
2942
+			&& ($include_primary_key
2943
+				|| $this->get_primary_key_field()
2944
+				   instanceof
2945
+				   EE_Primary_Key_String_Field)
2946
+			&& isset($fields_n_values[ $this->primary_key_name() ])
2947
+		) {
2948
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2949
+		}
2950
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2951
+			$uniqueness_where_params                              =
2952
+				array_intersect_key($fields_n_values, $unique_index->fields());
2953
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2954
+		}
2955
+		// if there is nothing to base this search on, then we shouldn't find anything
2956
+		if (empty($query_params)) {
2957
+			return [];
2958
+		}
2959
+		return $this->get_one($query_params);
2960
+	}
2961
+
2962
+
2963
+	/**
2964
+	 * Like count, but is optimized and returns a boolean instead of an int
2965
+	 *
2966
+	 * @param array $query_params
2967
+	 * @return boolean
2968
+	 * @throws EE_Error
2969
+	 * @throws ReflectionException
2970
+	 */
2971
+	public function exists($query_params)
2972
+	{
2973
+		$query_params['limit'] = 1;
2974
+		return $this->count($query_params) > 0;
2975
+	}
2976
+
2977
+
2978
+	/**
2979
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
2980
+	 *
2981
+	 * @param int|string $id
2982
+	 * @return boolean
2983
+	 * @throws EE_Error
2984
+	 * @throws ReflectionException
2985
+	 */
2986
+	public function exists_by_ID($id)
2987
+	{
2988
+		return $this->exists(
2989
+			[
2990
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
2991
+				[
2992
+					$this->primary_key_name() => $id,
2993
+				],
2994
+			]
2995
+		);
2996
+	}
2997
+
2998
+
2999
+	/**
3000
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3001
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3002
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3003
+	 * on the main table)
3004
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3005
+	 * cases where we want to call it directly rather than via insert().
3006
+	 *
3007
+	 * @access   protected
3008
+	 * @param EE_Table_Base $table
3009
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3010
+	 *                                       float
3011
+	 * @param int           $new_id          for now we assume only int keys
3012
+	 * @return int ID of new row inserted, or FALSE on failure
3013
+	 * @throws EE_Error
3014
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3015
+	 */
3016
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3017
+	{
3018
+		global $wpdb;
3019
+		$insertion_col_n_values = [];
3020
+		$format_for_insertion   = [];
3021
+		$fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3022
+		foreach ($fields_on_table as $field_name => $field_obj) {
3023
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3024
+			if ($field_obj->is_auto_increment()) {
3025
+				continue;
3026
+			}
3027
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3028
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3029
+			if ($prepared_value !== null) {
3030
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3031
+				$format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3032
+			}
3033
+		}
3034
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3035
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3036
+			// so add the fk to the main table as a column
3037
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3038
+			$format_for_insertion[]                              =
3039
+				'%d';// yes right now we're only allowing these foreign keys to be INTs
3040
+		}
3041
+		// insert the new entry
3042
+		$result = $this->_do_wpdb_query(
3043
+			'insert',
3044
+			[$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3045
+		);
3046
+		if ($result === false) {
3047
+			return false;
3048
+		}
3049
+		// ok, now what do we return for the ID of the newly-inserted thing?
3050
+		if ($this->has_primary_key_field()) {
3051
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3052
+				return $wpdb->insert_id;
3053
+			}
3054
+			// it's not an auto-increment primary key, so
3055
+			// it must have been supplied
3056
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3057
+		}
3058
+		// we can't return a  primary key because there is none. instead return
3059
+		// a unique string indicating this model
3060
+		return $this->get_index_primary_key_string($fields_n_values);
3061
+	}
3062
+
3063
+
3064
+	/**
3065
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3066
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3067
+	 * and there is no default, we pass it along. WPDB will take care of it)
3068
+	 *
3069
+	 * @param EE_Model_Field_Base $field_obj
3070
+	 * @param array               $fields_n_values
3071
+	 * @return mixed string|int|float depending on what the table column will be expecting
3072
+	 * @throws EE_Error
3073
+	 */
3074
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3075
+	{
3076
+		// if this field doesn't allow nullable, don't allow it
3077
+		if (
3078
+			! $field_obj->is_nullable()
3079
+			&& (
3080
+				! isset($fields_n_values[ $field_obj->get_name() ])
3081
+				|| $fields_n_values[ $field_obj->get_name() ] === null
3082
+			)
3083
+		) {
3084
+			$fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3085
+		}
3086
+		$unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3087
+			? $fields_n_values[ $field_obj->get_name() ]
3088
+			: null;
3089
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3090
+	}
3091
+
3092
+
3093
+	/**
3094
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3095
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3096
+	 * the field's prepare_for_set() method.
3097
+	 *
3098
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3099
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3100
+	 *                                   top of file)
3101
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3102
+	 *                                   $value is a custom selection
3103
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3104
+	 */
3105
+	private function _prepare_value_for_use_in_db($value, $field)
3106
+	{
3107
+		if ($field && $field instanceof EE_Model_Field_Base) {
3108
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3109
+			switch ($this->_values_already_prepared_by_model_object) {
3110
+				/** @noinspection PhpMissingBreakStatementInspection */
3111
+				case self::not_prepared_by_model_object:
3112
+					$value = $field->prepare_for_set($value);
3113
+				// purposefully left out "return"
3114
+				case self::prepared_by_model_object:
3115
+					/** @noinspection SuspiciousAssignmentsInspection */
3116
+					$value = $field->prepare_for_use_in_db($value);
3117
+				case self::prepared_for_use_in_db:
3118
+					// leave the value alone
3119
+			}
3120
+			return $value;
3121
+			// phpcs:enable
3122
+		}
3123
+		return $value;
3124
+	}
3125
+
3126
+
3127
+	/**
3128
+	 * Returns the main table on this model
3129
+	 *
3130
+	 * @return EE_Primary_Table
3131
+	 * @throws EE_Error
3132
+	 */
3133
+	protected function _get_main_table()
3134
+	{
3135
+		foreach ($this->_tables as $table) {
3136
+			if ($table instanceof EE_Primary_Table) {
3137
+				return $table;
3138
+			}
3139
+		}
3140
+		throw new EE_Error(
3141
+			sprintf(
3142
+				esc_html__(
3143
+					'There are no main tables on %s. They should be added to _tables array in the constructor',
3144
+					'event_espresso'
3145
+				),
3146
+				get_class($this)
3147
+			)
3148
+		);
3149
+	}
3150
+
3151
+
3152
+	/**
3153
+	 * table
3154
+	 * returns EE_Primary_Table table name
3155
+	 *
3156
+	 * @return string
3157
+	 * @throws EE_Error
3158
+	 */
3159
+	public function table()
3160
+	{
3161
+		return $this->_get_main_table()->get_table_name();
3162
+	}
3163
+
3164
+
3165
+	/**
3166
+	 * table
3167
+	 * returns first EE_Secondary_Table table name
3168
+	 *
3169
+	 * @return string
3170
+	 */
3171
+	public function second_table()
3172
+	{
3173
+		// grab second table from tables array
3174
+		$second_table = end($this->_tables);
3175
+		return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3176
+	}
3177
+
3178
+
3179
+	/**
3180
+	 * get_table_obj_by_alias
3181
+	 * returns table name given it's alias
3182
+	 *
3183
+	 * @param string $table_alias
3184
+	 * @return EE_Primary_Table | EE_Secondary_Table
3185
+	 */
3186
+	public function get_table_obj_by_alias($table_alias = '')
3187
+	{
3188
+		return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3189
+	}
3190
+
3191
+
3192
+	/**
3193
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3194
+	 *
3195
+	 * @return EE_Secondary_Table[]
3196
+	 */
3197
+	protected function _get_other_tables()
3198
+	{
3199
+		$other_tables = [];
3200
+		foreach ($this->_tables as $table_alias => $table) {
3201
+			if ($table instanceof EE_Secondary_Table) {
3202
+				$other_tables[ $table_alias ] = $table;
3203
+			}
3204
+		}
3205
+		return $other_tables;
3206
+	}
3207
+
3208
+
3209
+	/**
3210
+	 * Finds all the fields that correspond to the given table
3211
+	 *
3212
+	 * @param string $table_alias , array key in EEM_Base::_tables
3213
+	 * @return EE_Model_Field_Base[]
3214
+	 */
3215
+	public function _get_fields_for_table($table_alias)
3216
+	{
3217
+		return $this->_fields[ $table_alias ];
3218
+	}
3219
+
3220
+
3221
+	/**
3222
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3223
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3224
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3225
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3226
+	 * related Registration, Transaction, and Payment models.
3227
+	 *
3228
+	 * @param array $query_params see github link below for more info
3229
+	 * @return EE_Model_Query_Info_Carrier
3230
+	 * @throws EE_Error
3231
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3232
+	 */
3233
+	public function _extract_related_models_from_query($query_params)
3234
+	{
3235
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3236
+		if (array_key_exists(0, $query_params)) {
3237
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3238
+		}
3239
+		if (array_key_exists('group_by', $query_params)) {
3240
+			if (is_array($query_params['group_by'])) {
3241
+				$this->_extract_related_models_from_sub_params_array_values(
3242
+					$query_params['group_by'],
3243
+					$query_info_carrier,
3244
+					'group_by'
3245
+				);
3246
+			} elseif (! empty($query_params['group_by'])) {
3247
+				$this->_extract_related_model_info_from_query_param(
3248
+					$query_params['group_by'],
3249
+					$query_info_carrier,
3250
+					'group_by'
3251
+				);
3252
+			}
3253
+		}
3254
+		if (array_key_exists('having', $query_params)) {
3255
+			$this->_extract_related_models_from_sub_params_array_keys(
3256
+				$query_params[0],
3257
+				$query_info_carrier,
3258
+				'having'
3259
+			);
3260
+		}
3261
+		if (array_key_exists('order_by', $query_params)) {
3262
+			if (is_array($query_params['order_by'])) {
3263
+				$this->_extract_related_models_from_sub_params_array_keys(
3264
+					$query_params['order_by'],
3265
+					$query_info_carrier,
3266
+					'order_by'
3267
+				);
3268
+			} elseif (! empty($query_params['order_by'])) {
3269
+				$this->_extract_related_model_info_from_query_param(
3270
+					$query_params['order_by'],
3271
+					$query_info_carrier,
3272
+					'order_by'
3273
+				);
3274
+			}
3275
+		}
3276
+		if (array_key_exists('force_join', $query_params)) {
3277
+			$this->_extract_related_models_from_sub_params_array_values(
3278
+				$query_params['force_join'],
3279
+				$query_info_carrier,
3280
+				'force_join'
3281
+			);
3282
+		}
3283
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3284
+		return $query_info_carrier;
3285
+	}
3286
+
3287
+
3288
+	/**
3289
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3290
+	 *
3291
+	 * @param array                       $sub_query_params
3292
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3293
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3294
+	 * @return EE_Model_Query_Info_Carrier
3295
+	 * @throws EE_Error
3296
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3297
+	 */
3298
+	private function _extract_related_models_from_sub_params_array_keys(
3299
+		$sub_query_params,
3300
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3301
+		$query_param_type
3302
+	) {
3303
+		if (! empty($sub_query_params)) {
3304
+			$sub_query_params = (array)$sub_query_params;
3305
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3306
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3307
+				$this->_extract_related_model_info_from_query_param(
3308
+					$param,
3309
+					$model_query_info_carrier,
3310
+					$query_param_type
3311
+				);
3312
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3313
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3314
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3315
+				// of array('Registration.TXN_ID'=>23)
3316
+				$query_param_sans_stars =
3317
+					$this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3318
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3319
+					if (! is_array($possibly_array_of_params)) {
3320
+						throw new EE_Error(
3321
+							sprintf(
3322
+								esc_html__(
3323
+									"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'))",
3324
+									"event_espresso"
3325
+								),
3326
+								$param,
3327
+								$possibly_array_of_params
3328
+							)
3329
+						);
3330
+					}
3331
+					$this->_extract_related_models_from_sub_params_array_keys(
3332
+						$possibly_array_of_params,
3333
+						$model_query_info_carrier,
3334
+						$query_param_type
3335
+					);
3336
+				} elseif (
3337
+					$query_param_type === 0 // ie WHERE
3338
+					&& is_array($possibly_array_of_params)
3339
+					&& isset($possibly_array_of_params[2])
3340
+					&& $possibly_array_of_params[2] == true
3341
+				) {
3342
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3343
+					// indicating that $possible_array_of_params[1] is actually a field name,
3344
+					// from which we should extract query parameters!
3345
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3346
+						throw new EE_Error(
3347
+							sprintf(
3348
+								esc_html__(
3349
+									"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3350
+									"event_espresso"
3351
+								),
3352
+								$query_param_type,
3353
+								implode(",", $possibly_array_of_params)
3354
+							)
3355
+						);
3356
+					}
3357
+					$this->_extract_related_model_info_from_query_param(
3358
+						$possibly_array_of_params[1],
3359
+						$model_query_info_carrier,
3360
+						$query_param_type
3361
+					);
3362
+				}
3363
+			}
3364
+		}
3365
+		return $model_query_info_carrier;
3366
+	}
3367
+
3368
+
3369
+	/**
3370
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3371
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3372
+	 *
3373
+	 * @param array                       $sub_query_params
3374
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3375
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3376
+	 * @return EE_Model_Query_Info_Carrier
3377
+	 * @throws EE_Error
3378
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3379
+	 */
3380
+	private function _extract_related_models_from_sub_params_array_values(
3381
+		$sub_query_params,
3382
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3383
+		$query_param_type
3384
+	) {
3385
+		if (! empty($sub_query_params)) {
3386
+			if (! is_array($sub_query_params)) {
3387
+				throw new EE_Error(
3388
+					sprintf(
3389
+						esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3390
+						$sub_query_params
3391
+					)
3392
+				);
3393
+			}
3394
+			foreach ($sub_query_params as $param) {
3395
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3396
+				$this->_extract_related_model_info_from_query_param(
3397
+					$param,
3398
+					$model_query_info_carrier,
3399
+					$query_param_type
3400
+				);
3401
+			}
3402
+		}
3403
+		return $model_query_info_carrier;
3404
+	}
3405
+
3406
+
3407
+	/**
3408
+	 * Extract all the query parts from  model query params
3409
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3410
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3411
+	 * but use them in a different order. Eg, we need to know what models we are querying
3412
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3413
+	 * other models before we can finalize the where clause SQL.
3414
+	 *
3415
+	 * @param array $query_params see github link below for more info
3416
+	 * @return EE_Model_Query_Info_Carrier
3417
+	 * @throws EE_Error
3418
+	 * @throws ModelConfigurationException
3419
+	 * @throws ReflectionException
3420
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3421
+	 */
3422
+	public function _create_model_query_info_carrier($query_params)
3423
+	{
3424
+		if (! is_array($query_params)) {
3425
+			EE_Error::doing_it_wrong(
3426
+				'EEM_Base::_create_model_query_info_carrier',
3427
+				sprintf(
3428
+					esc_html__(
3429
+						'$query_params should be an array, you passed a variable of type %s',
3430
+						'event_espresso'
3431
+					),
3432
+					gettype($query_params)
3433
+				),
3434
+				'4.6.0'
3435
+			);
3436
+			$query_params = [];
3437
+		}
3438
+		$query_params[0] = isset($query_params[0]) ? $query_params[0] : [];
3439
+		// first check if we should alter the query to account for caps or not
3440
+		// because the caps might require us to do extra joins
3441
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3442
+			$query_params[0] = array_replace_recursive(
3443
+				$query_params[0],
3444
+				$this->caps_where_conditions(
3445
+					$query_params['caps']
3446
+				)
3447
+			);
3448
+		}
3449
+
3450
+		// check if we should alter the query to remove data related to protected
3451
+		// custom post types
3452
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3453
+			$where_param_key_for_password = $this->modelChainAndPassword();
3454
+			// only include if related to a cpt where no password has been set
3455
+			$query_params[0]['OR*nopassword'] = [
3456
+				$where_param_key_for_password       => '',
3457
+				$where_param_key_for_password . '*' => ['IS_NULL'],
3458
+			];
3459
+		}
3460
+		$query_object = $this->_extract_related_models_from_query($query_params);
3461
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3462
+		foreach ($query_params[0] as $key => $value) {
3463
+			if (is_int($key)) {
3464
+				throw new EE_Error(
3465
+					sprintf(
3466
+						esc_html__(
3467
+							"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.",
3468
+							"event_espresso"
3469
+						),
3470
+						$key,
3471
+						var_export($value, true),
3472
+						var_export($query_params, true),
3473
+						get_class($this)
3474
+					)
3475
+				);
3476
+			}
3477
+		}
3478
+		if (
3479
+			array_key_exists('default_where_conditions', $query_params)
3480
+			&& ! empty($query_params['default_where_conditions'])
3481
+		) {
3482
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3483
+		} else {
3484
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3485
+		}
3486
+		$query_params[0] = array_merge(
3487
+			$this->_get_default_where_conditions_for_models_in_query(
3488
+				$query_object,
3489
+				$use_default_where_conditions,
3490
+				$query_params[0]
3491
+			),
3492
+			$query_params[0]
3493
+		);
3494
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3495
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3496
+		// So we need to setup a subquery and use that for the main join.
3497
+		// Note for now this only works on the primary table for the model.
3498
+		// So for instance, you could set the limit array like this:
3499
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3500
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3501
+			$query_object->set_main_model_join_sql(
3502
+				$this->_construct_limit_join_select(
3503
+					$query_params['on_join_limit'][0],
3504
+					$query_params['on_join_limit'][1]
3505
+				)
3506
+			);
3507
+		}
3508
+		// set limit
3509
+		if (array_key_exists('limit', $query_params)) {
3510
+			if (is_array($query_params['limit'])) {
3511
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3512
+					$e = sprintf(
3513
+						esc_html__(
3514
+							"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)",
3515
+							"event_espresso"
3516
+						),
3517
+						http_build_query($query_params['limit'])
3518
+					);
3519
+					throw new EE_Error($e . "|" . $e);
3520
+				}
3521
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3522
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3523
+			} elseif (! empty($query_params['limit'])) {
3524
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3525
+			}
3526
+		}
3527
+		// set order by
3528
+		if (array_key_exists('order_by', $query_params)) {
3529
+			if (is_array($query_params['order_by'])) {
3530
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3531
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3532
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3533
+				if (array_key_exists('order', $query_params)) {
3534
+					throw new EE_Error(
3535
+						sprintf(
3536
+							esc_html__(
3537
+								"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 ",
3538
+								"event_espresso"
3539
+							),
3540
+							get_class($this),
3541
+							implode(", ", array_keys($query_params['order_by'])),
3542
+							implode(", ", $query_params['order_by']),
3543
+							$query_params['order']
3544
+						)
3545
+					);
3546
+				}
3547
+				$this->_extract_related_models_from_sub_params_array_keys(
3548
+					$query_params['order_by'],
3549
+					$query_object,
3550
+					'order_by'
3551
+				);
3552
+				// assume it's an array of fields to order by
3553
+				$order_array = [];
3554
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3555
+					$order         = $this->_extract_order($order);
3556
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3557
+				}
3558
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3559
+			} elseif (! empty($query_params['order_by'])) {
3560
+				$this->_extract_related_model_info_from_query_param(
3561
+					$query_params['order_by'],
3562
+					$query_object,
3563
+					'order',
3564
+					$query_params['order_by']
3565
+				);
3566
+				$order = isset($query_params['order'])
3567
+					? $this->_extract_order($query_params['order'])
3568
+					: 'DESC';
3569
+				$query_object->set_order_by_sql(
3570
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3571
+				);
3572
+			}
3573
+		}
3574
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3575
+		if (
3576
+			! array_key_exists('order_by', $query_params)
3577
+			&& array_key_exists('order', $query_params)
3578
+			&& ! empty($query_params['order'])
3579
+		) {
3580
+			$pk_field = $this->get_primary_key_field();
3581
+			$order    = $this->_extract_order($query_params['order']);
3582
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3583
+		}
3584
+		// set group by
3585
+		if (array_key_exists('group_by', $query_params)) {
3586
+			if (is_array($query_params['group_by'])) {
3587
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3588
+				$group_by_array = [];
3589
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3590
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3591
+				}
3592
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3593
+			} elseif (! empty($query_params['group_by'])) {
3594
+				$query_object->set_group_by_sql(
3595
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3596
+				);
3597
+			}
3598
+		}
3599
+		// set having
3600
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3601
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3602
+		}
3603
+		// now, just verify they didn't pass anything wack
3604
+		foreach ($query_params as $query_key => $query_value) {
3605
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3606
+				throw new EE_Error(
3607
+					sprintf(
3608
+						esc_html__(
3609
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3610
+							'event_espresso'
3611
+						),
3612
+						$query_key,
3613
+						get_class($this),
3614
+						//                      print_r( $this->_allowed_query_params, TRUE )
3615
+						implode(',', $this->_allowed_query_params)
3616
+					)
3617
+				);
3618
+			}
3619
+		}
3620
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3621
+		if (empty($main_model_join_sql)) {
3622
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3623
+		}
3624
+		return $query_object;
3625
+	}
3626
+
3627
+
3628
+	/**
3629
+	 * Gets the where conditions that should be imposed on the query based on the
3630
+	 * context (eg reading frontend, backend, edit or delete).
3631
+	 *
3632
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3633
+	 * @return array
3634
+	 * @throws EE_Error
3635
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3636
+	 */
3637
+	public function caps_where_conditions($context = self::caps_read)
3638
+	{
3639
+		EEM_Base::verify_is_valid_cap_context($context);
3640
+		$cap_where_conditions = [];
3641
+		$cap_restrictions     = $this->caps_missing($context);
3642
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3643
+			$cap_where_conditions = array_replace_recursive(
3644
+				$cap_where_conditions,
3645
+				$restriction_if_no_cap->get_default_where_conditions()
3646
+			);
3647
+		}
3648
+		return apply_filters(
3649
+			'FHEE__EEM_Base__caps_where_conditions__return',
3650
+			$cap_where_conditions,
3651
+			$this,
3652
+			$context,
3653
+			$cap_restrictions
3654
+		);
3655
+	}
3656
+
3657
+
3658
+	/**
3659
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3660
+	 * otherwise throws an exception
3661
+	 *
3662
+	 * @param string $should_be_order_string
3663
+	 * @return string either ASC, asc, DESC or desc
3664
+	 * @throws EE_Error
3665
+	 */
3666
+	private function _extract_order($should_be_order_string)
3667
+	{
3668
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3669
+			return $should_be_order_string;
3670
+		}
3671
+		throw new EE_Error(
3672
+			sprintf(
3673
+				esc_html__(
3674
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3675
+					"event_espresso"
3676
+				),
3677
+				get_class($this),
3678
+				$should_be_order_string
3679
+			)
3680
+		);
3681
+	}
3682
+
3683
+
3684
+	/**
3685
+	 * Looks at all the models which are included in this query, and asks each
3686
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3687
+	 * so they can be merged
3688
+	 *
3689
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3690
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3691
+	 *                                                                  'none' means NO default where conditions will
3692
+	 *                                                                  be used AT ALL during this query.
3693
+	 *                                                                  'other_models_only' means default where
3694
+	 *                                                                  conditions from other models will be used, but
3695
+	 *                                                                  not for this primary model. 'all', the default,
3696
+	 *                                                                  means default where conditions will apply as
3697
+	 *                                                                  normal
3698
+	 * @param array                       $where_query_params
3699
+	 * @return array
3700
+	 * @throws EE_Error
3701
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3702
+	 */
3703
+	private function _get_default_where_conditions_for_models_in_query(
3704
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3705
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3706
+		$where_query_params = []
3707
+	) {
3708
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3709
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3710
+			throw new EE_Error(
3711
+				sprintf(
3712
+					esc_html__(
3713
+						"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3714
+						"event_espresso"
3715
+					),
3716
+					$use_default_where_conditions,
3717
+					implode(", ", $allowed_used_default_where_conditions_values)
3718
+				)
3719
+			);
3720
+		}
3721
+		$universal_query_params = [];
3722
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions)) {
3723
+			$universal_query_params = $this->_get_default_where_conditions();
3724
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions)) {
3725
+			$universal_query_params = $this->_get_minimum_where_conditions();
3726
+		}
3727
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3728
+			$related_model = $this->get_related_model_obj($model_name);
3729
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3730
+				$related_model_universal_where_params =
3731
+					$related_model->_get_default_where_conditions($model_relation_path);
3732
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3733
+				$related_model_universal_where_params =
3734
+					$related_model->_get_minimum_where_conditions($model_relation_path);
3735
+			} else {
3736
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3737
+				continue;
3738
+			}
3739
+			$overrides              = $this->_override_defaults_or_make_null_friendly(
3740
+				$related_model_universal_where_params,
3741
+				$where_query_params,
3742
+				$related_model,
3743
+				$model_relation_path
3744
+			);
3745
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3746
+				$universal_query_params,
3747
+				$overrides
3748
+			);
3749
+		}
3750
+		return $universal_query_params;
3751
+	}
3752
+
3753
+
3754
+	/**
3755
+	 * Determines whether or not we should use default where conditions for the model in question
3756
+	 * (this model, or other related models).
3757
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3758
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3759
+	 * We should use default where conditions on related models when they requested to use default where conditions
3760
+	 * on all models, or specifically just on other related models
3761
+	 *
3762
+	 * @param      $default_where_conditions_value
3763
+	 * @param bool $for_this_model false means this is for OTHER related models
3764
+	 * @return bool
3765
+	 */
3766
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3767
+	{
3768
+		return (
3769
+				   $for_this_model
3770
+				   && in_array(
3771
+					   $default_where_conditions_value,
3772
+					   [
3773
+						   EEM_Base::default_where_conditions_all,
3774
+						   EEM_Base::default_where_conditions_this_only,
3775
+						   EEM_Base::default_where_conditions_minimum_others,
3776
+					   ],
3777
+					   true
3778
+				   )
3779
+			   )
3780
+			   || (
3781
+				   ! $for_this_model
3782
+				   && in_array(
3783
+					   $default_where_conditions_value,
3784
+					   [
3785
+						   EEM_Base::default_where_conditions_all,
3786
+						   EEM_Base::default_where_conditions_others_only,
3787
+					   ],
3788
+					   true
3789
+				   )
3790
+			   );
3791
+	}
3792
+
3793
+
3794
+	/**
3795
+	 * Determines whether or not we should use default minimum conditions for the model in question
3796
+	 * (this model, or other related models).
3797
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3798
+	 * where conditions.
3799
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3800
+	 * on this model or others
3801
+	 *
3802
+	 * @param      $default_where_conditions_value
3803
+	 * @param bool $for_this_model false means this is for OTHER related models
3804
+	 * @return bool
3805
+	 */
3806
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3807
+	{
3808
+		return (
3809
+				   $for_this_model
3810
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3811
+			   )
3812
+			   || (
3813
+				   ! $for_this_model
3814
+				   && in_array(
3815
+					   $default_where_conditions_value,
3816
+					   [
3817
+						   EEM_Base::default_where_conditions_minimum_others,
3818
+						   EEM_Base::default_where_conditions_minimum_all,
3819
+					   ],
3820
+					   true
3821
+				   )
3822
+			   );
3823
+	}
3824
+
3825
+
3826
+	/**
3827
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3828
+	 * then we also add a special where condition which allows for that model's primary key
3829
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3830
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3831
+	 *
3832
+	 * @param array    $default_where_conditions
3833
+	 * @param array    $provided_where_conditions
3834
+	 * @param EEM_Base $model
3835
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3836
+	 * @return array
3837
+	 * @throws EE_Error
3838
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3839
+	 */
3840
+	private function _override_defaults_or_make_null_friendly(
3841
+		$default_where_conditions,
3842
+		$provided_where_conditions,
3843
+		$model,
3844
+		$model_relation_path
3845
+	) {
3846
+		$null_friendly_where_conditions = [];
3847
+		$none_overridden                = true;
3848
+		$or_condition_key_for_defaults  = 'OR*' . get_class($model);
3849
+		foreach ($default_where_conditions as $key => $val) {
3850
+			if (isset($provided_where_conditions[ $key ])) {
3851
+				$none_overridden = false;
3852
+			} else {
3853
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3854
+			}
3855
+		}
3856
+		if ($none_overridden && $default_where_conditions) {
3857
+			if ($model->has_primary_key_field()) {
3858
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3859
+																				   . "."
3860
+																				   . $model->primary_key_name() ] =
3861
+					['IS NULL'];
3862
+			}/*else{
3863 3863
                 //@todo NO PK, use other defaults
3864 3864
             }*/
3865
-        }
3866
-        return $null_friendly_where_conditions;
3867
-    }
3868
-
3869
-
3870
-    /**
3871
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3872
-     * default where conditions on all get_all, update, and delete queries done by this model.
3873
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3874
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3875
-     *
3876
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3877
-     * @return array
3878
-     * @throws EE_Error
3879
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3880
-     */
3881
-    private function _get_default_where_conditions($model_relation_path = '')
3882
-    {
3883
-        if ($this->_ignore_where_strategy) {
3884
-            return [];
3885
-        }
3886
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3887
-    }
3888
-
3889
-
3890
-    /**
3891
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3892
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3893
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3894
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3895
-     * Similar to _get_default_where_conditions
3896
-     *
3897
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3898
-     * @return array
3899
-     * @throws EE_Error
3900
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3901
-     */
3902
-    protected function _get_minimum_where_conditions($model_relation_path = '')
3903
-    {
3904
-        if ($this->_ignore_where_strategy) {
3905
-            return [];
3906
-        }
3907
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3908
-    }
3909
-
3910
-
3911
-    /**
3912
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3913
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3914
-     *
3915
-     * @param EE_Model_Query_Info_Carrier $model_query_info
3916
-     * @return string
3917
-     * @throws EE_Error
3918
-     */
3919
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3920
-    {
3921
-        $selects = $this->_get_columns_to_select_for_this_model();
3922
-        foreach (
3923
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3924
-        ) {
3925
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3926
-            $other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3927
-            foreach ($other_model_selects as $key => $value) {
3928
-                $selects[] = $value;
3929
-            }
3930
-        }
3931
-        return implode(", ", $selects);
3932
-    }
3933
-
3934
-
3935
-    /**
3936
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3937
-     * So that's going to be the columns for all the fields on the model
3938
-     *
3939
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
3940
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3941
-     */
3942
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3943
-    {
3944
-        $fields                                       = $this->field_settings();
3945
-        $selects                                      = [];
3946
-        $table_alias_with_model_relation_chain_prefix =
3947
-            EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3948
-                $model_relation_chain,
3949
-                $this->get_this_model_name()
3950
-            );
3951
-        foreach ($fields as $field_obj) {
3952
-            $selects[] = $table_alias_with_model_relation_chain_prefix
3953
-                         . $field_obj->get_table_alias()
3954
-                         . "."
3955
-                         . $field_obj->get_table_column()
3956
-                         . " AS '"
3957
-                         . $table_alias_with_model_relation_chain_prefix
3958
-                         . $field_obj->get_table_alias()
3959
-                         . "."
3960
-                         . $field_obj->get_table_column()
3961
-                         . "'";
3962
-        }
3963
-        // make sure we are also getting the PKs of each table
3964
-        $tables = $this->get_tables();
3965
-        if (count($tables) > 1) {
3966
-            foreach ($tables as $table_obj) {
3967
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3968
-                                       . $table_obj->get_fully_qualified_pk_column();
3969
-                if (! in_array($qualified_pk_column, $selects)) {
3970
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3971
-                }
3972
-            }
3973
-        }
3974
-        return $selects;
3975
-    }
3976
-
3977
-
3978
-    /**
3979
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3980
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3981
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3982
-     * SQL for joining, and the data types
3983
-     *
3984
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
3985
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3986
-     * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
3987
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3988
-     *                                                          column name. We only want model names, eg 'Event.Venue'
3989
-     *                                                          or 'Registration's
3990
-     * @param string|null                 $original_query_param what it originally was (eg
3991
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3992
-     *                                                          matches $query_param
3993
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3994
-     * @throws EE_Error
3995
-     */
3996
-    private function _extract_related_model_info_from_query_param(
3997
-        $query_param,
3998
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
3999
-        $query_param_type,
4000
-        $original_query_param = null
4001
-    ) {
4002
-        if ($original_query_param === null) {
4003
-            $original_query_param = $query_param;
4004
-        }
4005
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4006
-        // whether or not to allow logic_query_params like 'NOT','OR', or 'AND'
4007
-        $allow_logic_query_params = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4008
-        $allow_fields             = in_array(
4009
-            $query_param_type,
4010
-            ['where', 'having', 'order_by', 'group_by', 'order', 'custom_selects', 0],
4011
-            true
4012
-        );
4013
-        // check to see if we have a field on this model
4014
-        $this_model_fields = $this->field_settings(true);
4015
-        if (array_key_exists($query_param, $this_model_fields)) {
4016
-            if ($allow_fields) {
4017
-                return;
4018
-            }
4019
-            throw new EE_Error(
4020
-                sprintf(
4021
-                    esc_html__(
4022
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4023
-                        "event_espresso"
4024
-                    ),
4025
-                    $query_param,
4026
-                    get_class($this),
4027
-                    $query_param_type,
4028
-                    $original_query_param
4029
-                )
4030
-            );
4031
-        }
4032
-        // check if this is a special logic query param
4033
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4034
-            if ($allow_logic_query_params) {
4035
-                return;
4036
-            }
4037
-            throw new EE_Error(
4038
-                sprintf(
4039
-                    esc_html__(
4040
-                        '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',
4041
-                        'event_espresso'
4042
-                    ),
4043
-                    implode('", "', $this->_logic_query_param_keys),
4044
-                    $query_param,
4045
-                    get_class($this),
4046
-                    '<br />',
4047
-                    "\t"
4048
-                    . ' $passed_in_query_info = <pre>'
4049
-                    . print_r($passed_in_query_info, true)
4050
-                    . '</pre>'
4051
-                    . "\n\t"
4052
-                    . ' $query_param_type = '
4053
-                    . $query_param_type
4054
-                    . "\n\t"
4055
-                    . ' $original_query_param = '
4056
-                    . $original_query_param
4057
-                )
4058
-            );
4059
-        }
4060
-        // check if it's a custom selection
4061
-        if (
4062
-            $this->_custom_selections instanceof CustomSelects
4063
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4064
-        ) {
4065
-            return;
4066
-        }
4067
-        // check if has a model name at the beginning
4068
-        // and
4069
-        // check if it's a field on a related model
4070
-        if (
4071
-        $this->extractJoinModelFromQueryParams(
4072
-            $passed_in_query_info,
4073
-            $query_param,
4074
-            $original_query_param,
4075
-            $query_param_type
4076
-        )
4077
-        ) {
4078
-            return;
4079
-        }
4080
-
4081
-        // ok so $query_param didn't start with a model name
4082
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4083
-        // it's wack, that's what it is
4084
-        throw new EE_Error(
4085
-            sprintf(
4086
-                esc_html__(
4087
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4088
-                    "event_espresso"
4089
-                ),
4090
-                $query_param,
4091
-                get_class($this),
4092
-                $query_param_type,
4093
-                $original_query_param
4094
-            )
4095
-        );
4096
-    }
4097
-
4098
-
4099
-    /**
4100
-     * Extracts any possible join model information from the provided possible_join_string.
4101
-     * This method will read the provided $possible_join_string value and determine if there are any possible model
4102
-     * join
4103
-     * parts that should be added to the query.
4104
-     *
4105
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4106
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4107
-     * @param null|string                 $original_query_param
4108
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4109
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4110
-     *                                                           etc.)
4111
-     * @return bool  returns true if a join was added and false if not.
4112
-     * @throws EE_Error
4113
-     */
4114
-    private function extractJoinModelFromQueryParams(
4115
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4116
-        $possible_join_string,
4117
-        $original_query_param,
4118
-        $query_parameter_type
4119
-    ) {
4120
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4121
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4122
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4123
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4124
-                if ($possible_join_string === '') {
4125
-                    // nothing left to $query_param
4126
-                    // we should actually end in a field name, not a model like this!
4127
-                    throw new EE_Error(
4128
-                        sprintf(
4129
-                            esc_html__(
4130
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4131
-                                "event_espresso"
4132
-                            ),
4133
-                            $possible_join_string,
4134
-                            $query_parameter_type,
4135
-                            get_class($this),
4136
-                            $valid_related_model_name
4137
-                        )
4138
-                    );
4139
-                }
4140
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4141
-                $related_model_obj->_extract_related_model_info_from_query_param(
4142
-                    $possible_join_string,
4143
-                    $query_info_carrier,
4144
-                    $query_parameter_type,
4145
-                    $original_query_param
4146
-                );
4147
-                return true;
4148
-            }
4149
-            if ($possible_join_string === $valid_related_model_name) {
4150
-                $this->_add_join_to_model(
4151
-                    $valid_related_model_name,
4152
-                    $query_info_carrier,
4153
-                    $original_query_param
4154
-                );
4155
-                return true;
4156
-            }
4157
-        }
4158
-        return false;
4159
-    }
4160
-
4161
-
4162
-    /**
4163
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4164
-     *
4165
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4166
-     * @throws EE_Error
4167
-     */
4168
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4169
-    {
4170
-        if (
4171
-            $this->_custom_selections instanceof CustomSelects
4172
-            && ($this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4173
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4174
-            )
4175
-        ) {
4176
-            $original_selects = $this->_custom_selections->originalSelects();
4177
-            foreach ($original_selects as $alias => $select_configuration) {
4178
-                $this->extractJoinModelFromQueryParams(
4179
-                    $query_info_carrier,
4180
-                    $select_configuration[0],
4181
-                    $select_configuration[0],
4182
-                    'custom_selects'
4183
-                );
4184
-            }
4185
-        }
4186
-    }
4187
-
4188
-
4189
-    /**
4190
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4191
-     * and store it on $passed_in_query_info
4192
-     *
4193
-     * @param string                      $model_name
4194
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4195
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4196
-     *                                                          model and $model_name. Eg, if we are querying Event,
4197
-     *                                                          and are adding a join to 'Payment' with the original
4198
-     *                                                          query param key
4199
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4200
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4201
-     *                                                          Payment wants to add default query params so that it
4202
-     *                                                          will know what models to prepend onto its default query
4203
-     *                                                          params or in case it wants to rename tables (in case
4204
-     *                                                          there are multiple joins to the same table)
4205
-     * @return void
4206
-     * @throws EE_Error
4207
-     */
4208
-    private function _add_join_to_model(
4209
-        $model_name,
4210
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4211
-        $original_query_param
4212
-    ) {
4213
-        $relation_obj         = $this->related_settings_for($model_name);
4214
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4215
-        // check if the relation is HABTM, because then we're essentially doing two joins
4216
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4217
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4218
-            $join_model_obj = $relation_obj->get_join_model();
4219
-            // replace the model specified with the join model for this relation chain, whi
4220
-            $relation_chain_to_join_model =
4221
-                EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4222
-                    $model_name,
4223
-                    $join_model_obj->get_this_model_name(),
4224
-                    $model_relation_chain
4225
-                );
4226
-            $passed_in_query_info->merge(
4227
-                new EE_Model_Query_Info_Carrier(
4228
-                    [$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4229
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4230
-                )
4231
-            );
4232
-        }
4233
-        // now just join to the other table pointed to by the relation object, and add its data types
4234
-        $passed_in_query_info->merge(
4235
-            new EE_Model_Query_Info_Carrier(
4236
-                [$model_relation_chain => $model_name],
4237
-                $relation_obj->get_join_statement($model_relation_chain)
4238
-            )
4239
-        );
4240
-    }
4241
-
4242
-
4243
-    /**
4244
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4245
-     *
4246
-     * @param array $where_params
4247
-     * @return string of SQL
4248
-     * @throws EE_Error
4249
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4250
-     */
4251
-    private function _construct_where_clause($where_params)
4252
-    {
4253
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4254
-        if ($SQL) {
4255
-            return " WHERE " . $SQL;
4256
-        }
4257
-        return '';
4258
-    }
4259
-
4260
-
4261
-    /**
4262
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4263
-     * and should be passed HAVING parameters, not WHERE parameters
4264
-     *
4265
-     * @param array $having_params
4266
-     * @return string
4267
-     * @throws EE_Error
4268
-     */
4269
-    private function _construct_having_clause($having_params)
4270
-    {
4271
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4272
-        if ($SQL) {
4273
-            return " HAVING " . $SQL;
4274
-        }
4275
-        return '';
4276
-    }
4277
-
4278
-
4279
-    /**
4280
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4281
-     * Event_Meta.meta_value = 'foo'))"
4282
-     *
4283
-     * @param array  $where_params
4284
-     * @param string $glue joins each sub-clause together. Should really only be " AND " or " OR "...
4285
-     * @return string of SQL
4286
-     * @throws EE_Error
4287
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4288
-     */
4289
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4290
-    {
4291
-        $where_clauses = [];
4292
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4293
-            $query_param =
4294
-                $this->_remove_stars_and_anything_after_from_condition_query_param_key(
4295
-                    $query_param
4296
-                );// str_replace("*",'',$query_param);
4297
-            if (in_array($query_param, $this->_logic_query_param_keys)) {
4298
-                switch ($query_param) {
4299
-                    case 'not':
4300
-                    case 'NOT':
4301
-                        $where_clauses[] = "! ("
4302
-                                           . $this->_construct_condition_clause_recursive(
4303
-                                $op_and_value_or_sub_condition,
4304
-                                $glue
4305
-                            )
4306
-                                           . ")";
4307
-                        break;
4308
-                    case 'and':
4309
-                    case 'AND':
4310
-                        $where_clauses[] = " ("
4311
-                                           . $this->_construct_condition_clause_recursive(
4312
-                                $op_and_value_or_sub_condition,
4313
-                                ' AND '
4314
-                            )
4315
-                                           . ")";
4316
-                        break;
4317
-                    case 'or':
4318
-                    case 'OR':
4319
-                        $where_clauses[] = " ("
4320
-                                           . $this->_construct_condition_clause_recursive(
4321
-                                $op_and_value_or_sub_condition,
4322
-                                ' OR '
4323
-                            )
4324
-                                           . ")";
4325
-                        break;
4326
-                }
4327
-            } else {
4328
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4329
-                // if it's not a normal field, maybe it's a custom selection?
4330
-                if (! $field_obj) {
4331
-                    if ($this->_custom_selections instanceof CustomSelects) {
4332
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4333
-                    } else {
4334
-                        throw new EE_Error(
4335
-                            sprintf(
4336
-                                esc_html__(
4337
-                                    "%s is neither a valid model field name, nor a custom selection",
4338
-                                    "event_espresso"
4339
-                                ),
4340
-                                $query_param
4341
-                            )
4342
-                        );
4343
-                    }
4344
-                }
4345
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4346
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4347
-            }
4348
-        }
4349
-        return $where_clauses ? implode($glue, $where_clauses) : '';
4350
-    }
4351
-
4352
-
4353
-    /**
4354
-     * Takes the input parameter and extract the table name (alias) and column name
4355
-     *
4356
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4357
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4358
-     * @throws EE_Error
4359
-     */
4360
-    private function _deduce_column_name_from_query_param($query_param)
4361
-    {
4362
-        $field = $this->_deduce_field_from_query_param($query_param);
4363
-        if ($field) {
4364
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4365
-                $field->get_model_name(),
4366
-                $query_param
4367
-            );
4368
-            return $table_alias_prefix . $field->get_qualified_column();
4369
-        }
4370
-        if (
4371
-            $this->_custom_selections instanceof CustomSelects
4372
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4373
-        ) {
4374
-            // maybe it's custom selection item?
4375
-            // if so, just use it as the "column name"
4376
-            return $query_param;
4377
-        }
4378
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4379
-            ? implode(',', $this->_custom_selections->columnAliases())
4380
-            : '';
4381
-        throw new EE_Error(
4382
-            sprintf(
4383
-                esc_html__(
4384
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4385
-                    "event_espresso"
4386
-                ),
4387
-                $query_param,
4388
-                $custom_select_aliases
4389
-            )
4390
-        );
4391
-    }
4392
-
4393
-
4394
-    /**
4395
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4396
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4397
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4398
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4399
-     *
4400
-     * @param string $condition_query_param_key
4401
-     * @return string
4402
-     */
4403
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4404
-    {
4405
-        $pos_of_star = strpos($condition_query_param_key, '*');
4406
-        if ($pos_of_star === false) {
4407
-            return $condition_query_param_key;
4408
-        }
4409
-        return substr($condition_query_param_key, 0, $pos_of_star);
4410
-    }
4411
-
4412
-
4413
-    /**
4414
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4415
-     *
4416
-     * @param mixed      array | string    $op_and_value
4417
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4418
-     * @return string
4419
-     * @throws EE_Error
4420
-     */
4421
-    private function _construct_op_and_value($op_and_value, $field_obj)
4422
-    {
4423
-        if (is_array($op_and_value)) {
4424
-            $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4425
-            if (! $operator) {
4426
-                $php_array_like_string = [];
4427
-                foreach ($op_and_value as $key => $value) {
4428
-                    $php_array_like_string[] = "$key=>$value";
4429
-                }
4430
-                throw new EE_Error(
4431
-                    sprintf(
4432
-                        esc_html__(
4433
-                            "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))",
4434
-                            "event_espresso"
4435
-                        ),
4436
-                        implode(",", $php_array_like_string)
4437
-                    )
4438
-                );
4439
-            }
4440
-            $value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4441
-        } else {
4442
-            $operator = '=';
4443
-            $value    = $op_and_value;
4444
-        }
4445
-        // check to see if the value is actually another field
4446
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4447
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4448
-        }
4449
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4450
-            // in this case, the value should be an array, or at least a comma-separated list
4451
-            // it will need to handle a little differently
4452
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4453
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4454
-            return $operator . SP . $cleaned_value;
4455
-        }
4456
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4457
-            // the value should be an array with count of two.
4458
-            if (count($value) !== 2) {
4459
-                throw new EE_Error(
4460
-                    sprintf(
4461
-                        esc_html__(
4462
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4463
-                            'event_espresso'
4464
-                        ),
4465
-                        "BETWEEN"
4466
-                    )
4467
-                );
4468
-            }
4469
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4470
-            return $operator . SP . $cleaned_value;
4471
-        }
4472
-        if (in_array($operator, $this->valid_null_style_operators())) {
4473
-            if ($value !== null) {
4474
-                throw new EE_Error(
4475
-                    sprintf(
4476
-                        esc_html__(
4477
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4478
-                            "event_espresso"
4479
-                        ),
4480
-                        $value,
4481
-                        $operator
4482
-                    )
4483
-                );
4484
-            }
4485
-            return $operator;
4486
-        }
4487
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4488
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4489
-            // remove other junk. So just treat it as a string.
4490
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4491
-        }
4492
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4493
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4494
-        }
4495
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4496
-            throw new EE_Error(
4497
-                sprintf(
4498
-                    esc_html__(
4499
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4500
-                        'event_espresso'
4501
-                    ),
4502
-                    $operator,
4503
-                    $operator
4504
-                )
4505
-            );
4506
-        }
4507
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4508
-            throw new EE_Error(
4509
-                sprintf(
4510
-                    esc_html__(
4511
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4512
-                        'event_espresso'
4513
-                    ),
4514
-                    $operator,
4515
-                    $operator
4516
-                )
4517
-            );
4518
-        }
4519
-        throw new EE_Error(
4520
-            sprintf(
4521
-                esc_html__(
4522
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4523
-                    "event_espresso"
4524
-                ),
4525
-                http_build_query($op_and_value)
4526
-            )
4527
-        );
4528
-    }
4529
-
4530
-
4531
-    /**
4532
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4533
-     *
4534
-     * @param array                      $values
4535
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4536
-     *                                              '%s'
4537
-     * @return string
4538
-     * @throws EE_Error
4539
-     */
4540
-    public function _construct_between_value($values, $field_obj)
4541
-    {
4542
-        $cleaned_values = [];
4543
-        foreach ($values as $value) {
4544
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4545
-        }
4546
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4547
-    }
4548
-
4549
-
4550
-    /**
4551
-     * Takes an array or a comma-separated list of $values and cleans them
4552
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4553
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4554
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4555
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4556
-     *
4557
-     * @param mixed                      $values    array or comma-separated string
4558
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4559
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4560
-     * @throws EE_Error
4561
-     */
4562
-    public function _construct_in_value($values, $field_obj)
4563
-    {
4564
-        // check if the value is a CSV list
4565
-        if (is_string($values)) {
4566
-            // in which case, turn it into an array
4567
-            $values = explode(",", $values);
4568
-        }
4569
-        $cleaned_values = [];
4570
-        foreach ($values as $value) {
4571
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4572
-        }
4573
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4574
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4575
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4576
-        if (empty($cleaned_values)) {
4577
-            $all_fields       = $this->field_settings();
4578
-            $a_field          = array_shift($all_fields);
4579
-            $main_table       = $this->_get_main_table();
4580
-            $cleaned_values[] = "SELECT "
4581
-                                . $a_field->get_table_column()
4582
-                                . " FROM "
4583
-                                . $main_table->get_table_name()
4584
-                                . " WHERE FALSE";
4585
-        }
4586
-        return "(" . implode(",", $cleaned_values) . ")";
4587
-    }
4588
-
4589
-
4590
-    /**
4591
-     * @param mixed                      $value
4592
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4593
-     * @return false|null|string
4594
-     * @throws EE_Error
4595
-     */
4596
-    private function _wpdb_prepare_using_field($value, $field_obj)
4597
-    {
4598
-        global $wpdb;
4599
-        if ($field_obj instanceof EE_Model_Field_Base) {
4600
-            return $wpdb->prepare(
4601
-                $field_obj->get_wpdb_data_type(),
4602
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4603
-            );
4604
-        } //$field_obj should really just be a data type
4605
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4606
-            throw new EE_Error(
4607
-                sprintf(
4608
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4609
-                    $field_obj,
4610
-                    implode(",", $this->_valid_wpdb_data_types)
4611
-                )
4612
-            );
4613
-        }
4614
-        return $wpdb->prepare($field_obj, $value);
4615
-    }
4616
-
4617
-
4618
-    /**
4619
-     * Takes the input parameter and finds the model field that it indicates.
4620
-     *
4621
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4622
-     * @return EE_Model_Field_Base
4623
-     * @throws EE_Error
4624
-     */
4625
-    protected function _deduce_field_from_query_param($query_param_name)
4626
-    {
4627
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4628
-        // which will help us find the database table and column
4629
-        $query_param_parts = explode(".", $query_param_name);
4630
-        if (empty($query_param_parts)) {
4631
-            throw new EE_Error(
4632
-                sprintf(
4633
-                    esc_html__(
4634
-                        "_extract_column_name is empty when trying to extract column and table name from %s",
4635
-                        'event_espresso'
4636
-                    ),
4637
-                    $query_param_name
4638
-                )
4639
-            );
4640
-        }
4641
-        $number_of_parts       = count($query_param_parts);
4642
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4643
-        if ($number_of_parts === 1) {
4644
-            $field_name = $last_query_param_part;
4645
-            $model_obj  = $this;
4646
-        } else {// $number_of_parts >= 2
4647
-            // the last part is the column name, and there are only 2parts. therefore...
4648
-            $field_name = $last_query_param_part;
4649
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4650
-        }
4651
-        try {
4652
-            return $model_obj->field_settings_for($field_name);
4653
-        } catch (EE_Error $e) {
4654
-            return null;
4655
-        }
4656
-    }
4657
-
4658
-
4659
-    /**
4660
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4661
-     * alias and column which corresponds to it
4662
-     *
4663
-     * @param string $field_name
4664
-     * @return string
4665
-     * @throws EE_Error
4666
-     */
4667
-    public function _get_qualified_column_for_field($field_name)
4668
-    {
4669
-        $all_fields = $this->field_settings();
4670
-        $field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4671
-        if ($field) {
4672
-            return $field->get_qualified_column();
4673
-        }
4674
-        throw new EE_Error(
4675
-            sprintf(
4676
-                esc_html__(
4677
-                    "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.",
4678
-                    'event_espresso'
4679
-                ),
4680
-                $field_name,
4681
-                get_class($this)
4682
-            )
4683
-        );
4684
-    }
4685
-
4686
-
4687
-    /**
4688
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4689
-     * Example usage:
4690
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4691
-     *      array(),
4692
-     *      ARRAY_A,
4693
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4694
-     *  );
4695
-     * is equivalent to
4696
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4697
-     * and
4698
-     *  EEM_Event::instance()->get_all_wpdb_results(
4699
-     *      array(
4700
-     *          array(
4701
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4702
-     *          ),
4703
-     *          ARRAY_A,
4704
-     *          implode(
4705
-     *              ', ',
4706
-     *              array_merge(
4707
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4708
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4709
-     *              )
4710
-     *          )
4711
-     *      )
4712
-     *  );
4713
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4714
-     *
4715
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4716
-     *                                            and the one whose fields you are selecting for example: when querying
4717
-     *                                            tickets model and selecting fields from the tickets model you would
4718
-     *                                            leave this parameter empty, because no models are needed to join
4719
-     *                                            between the queried model and the selected one. Likewise when
4720
-     *                                            querying the datetime model and selecting fields from the tickets
4721
-     *                                            model, it would also be left empty, because there is a direct
4722
-     *                                            relation from datetimes to tickets, so no model is needed to join
4723
-     *                                            them together. However, when querying from the event model and
4724
-     *                                            selecting fields from the ticket model, you should provide the string
4725
-     *                                            'Datetime', indicating that the event model must first join to the
4726
-     *                                            datetime model in order to find its relation to ticket model.
4727
-     *                                            Also, when querying from the venue model and selecting fields from
4728
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4729
-     *                                            indicating you need to join the venue model to the event model,
4730
-     *                                            to the datetime model, in order to find its relation to the ticket
4731
-     *                                            model. This string is used to deduce the prefix that gets added onto
4732
-     *                                            the models' tables qualified columns
4733
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4734
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4735
-     *                                            qualified column names
4736
-     * @return array|string
4737
-     */
4738
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4739
-    {
4740
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4741
-        $qualified_columns = [];
4742
-        foreach ($this->field_settings() as $field_name => $field) {
4743
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4744
-        }
4745
-        return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4746
-    }
4747
-
4748
-
4749
-    /**
4750
-     * constructs the select use on special limit joins
4751
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4752
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4753
-     * (as that is typically where the limits would be set).
4754
-     *
4755
-     * @param string       $table_alias The table the select is being built for
4756
-     * @param mixed|string $limit       The limit for this select
4757
-     * @return string                The final select join element for the query.
4758
-     * @throws EE_Error
4759
-     */
4760
-    public function _construct_limit_join_select($table_alias, $limit)
4761
-    {
4762
-        $SQL = '';
4763
-        foreach ($this->_tables as $table_obj) {
4764
-            if ($table_obj instanceof EE_Primary_Table) {
4765
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4766
-                    ? $table_obj->get_select_join_limit($limit)
4767
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4768
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4769
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4770
-                    ? $table_obj->get_select_join_limit_join($limit)
4771
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4772
-            }
4773
-        }
4774
-        return $SQL;
4775
-    }
4776
-
4777
-
4778
-    /**
4779
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4780
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4781
-     *
4782
-     * @return string SQL
4783
-     * @throws EE_Error
4784
-     */
4785
-    public function _construct_internal_join()
4786
-    {
4787
-        $SQL = $this->_get_main_table()->get_table_sql();
4788
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4789
-        return $SQL;
4790
-    }
4791
-
4792
-
4793
-    /**
4794
-     * Constructs the SQL for joining all the tables on this model.
4795
-     * Normally $alias should be the primary table's alias, but in cases where
4796
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4797
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4798
-     * alias, this will construct SQL like:
4799
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4800
-     * With $alias being a secondary table's alias, this will construct SQL like:
4801
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4802
-     *
4803
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4804
-     * @return string
4805
-     * @throws EE_Error
4806
-     */
4807
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4808
-    {
4809
-        $SQL               = '';
4810
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4811
-        foreach ($this->_tables as $table_obj) {
4812
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4813
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4814
-                    // so we're joining to this table, meaning the table is already in
4815
-                    // the FROM statement, BUT the primary table isn't. So we want
4816
-                    // to add the inverse join sql
4817
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4818
-                } else {
4819
-                    // just add a regular JOIN to this table from the primary table
4820
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4821
-                }
4822
-            }//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4823
-        }
4824
-        return $SQL;
4825
-    }
4826
-
4827
-
4828
-    /**
4829
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4830
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4831
-     * their data type (eg, '%s', '%d', etc)
4832
-     *
4833
-     * @return array
4834
-     */
4835
-    public function _get_data_types()
4836
-    {
4837
-        $data_types = [];
4838
-        foreach ($this->field_settings() as $field_obj) {
4839
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4840
-            /** @var $field_obj EE_Model_Field_Base */
4841
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4842
-        }
4843
-        return $data_types;
4844
-    }
4845
-
4846
-
4847
-    /**
4848
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4849
-     *
4850
-     * @param string $model_name
4851
-     * @return EEM_Base
4852
-     * @throws EE_Error
4853
-     */
4854
-    public function get_related_model_obj($model_name)
4855
-    {
4856
-        $model_classname = "EEM_" . $model_name;
4857
-        if (! class_exists($model_classname)) {
4858
-            throw new EE_Error(
4859
-                sprintf(
4860
-                    esc_html__(
4861
-                        "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4862
-                        'event_espresso'
4863
-                    ),
4864
-                    $model_name,
4865
-                    $model_classname
4866
-                )
4867
-            );
4868
-        }
4869
-        return call_user_func($model_classname . "::instance");
4870
-    }
4871
-
4872
-
4873
-    /**
4874
-     * Returns the array of EE_ModelRelations for this model.
4875
-     *
4876
-     * @return EE_Model_Relation_Base[]
4877
-     */
4878
-    public function relation_settings()
4879
-    {
4880
-        return $this->_model_relations;
4881
-    }
4882
-
4883
-
4884
-    /**
4885
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4886
-     * because without THOSE models, this model probably doesn't have much purpose.
4887
-     * (Eg, without an event, datetimes have little purpose.)
4888
-     *
4889
-     * @return EE_Belongs_To_Relation[]
4890
-     */
4891
-    public function belongs_to_relations()
4892
-    {
4893
-        $belongs_to_relations = [];
4894
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4895
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
4896
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4897
-            }
4898
-        }
4899
-        return $belongs_to_relations;
4900
-    }
4901
-
4902
-
4903
-    /**
4904
-     * Returns the specified EE_Model_Relation, or throws an exception
4905
-     *
4906
-     * @param string $relation_name name of relation, key in $this->_relatedModels
4907
-     * @return EE_Model_Relation_Base
4908
-     * @throws EE_Error
4909
-     */
4910
-    public function related_settings_for($relation_name)
4911
-    {
4912
-        $relatedModels = $this->relation_settings();
4913
-        if (! array_key_exists($relation_name, $relatedModels)) {
4914
-            throw new EE_Error(
4915
-                sprintf(
4916
-                    esc_html__(
4917
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4918
-                        'event_espresso'
4919
-                    ),
4920
-                    $relation_name,
4921
-                    $this->_get_class_name(),
4922
-                    implode(', ', array_keys($relatedModels))
4923
-                )
4924
-            );
4925
-        }
4926
-        return $relatedModels[ $relation_name ];
4927
-    }
4928
-
4929
-
4930
-    /**
4931
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4932
-     * fields
4933
-     *
4934
-     * @param string  $fieldName
4935
-     * @param boolean $include_db_only_fields
4936
-     * @return EE_Model_Field_Base
4937
-     * @throws EE_Error
4938
-     */
4939
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
4940
-    {
4941
-        $fieldSettings = $this->field_settings($include_db_only_fields);
4942
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4943
-            throw new EE_Error(
4944
-                sprintf(
4945
-                    esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4946
-                    $fieldName,
4947
-                    get_class($this)
4948
-                )
4949
-            );
4950
-        }
4951
-        return $fieldSettings[ $fieldName ];
4952
-    }
4953
-
4954
-
4955
-    /**
4956
-     * Checks if this field exists on this model
4957
-     *
4958
-     * @param string $fieldName a key in the model's _field_settings array
4959
-     * @return boolean
4960
-     */
4961
-    public function has_field($fieldName)
4962
-    {
4963
-        $fieldSettings = $this->field_settings(true);
4964
-        if (isset($fieldSettings[ $fieldName ])) {
4965
-            return true;
4966
-        }
4967
-        return false;
4968
-    }
4969
-
4970
-
4971
-    /**
4972
-     * Returns whether or not this model has a relation to the specified model
4973
-     *
4974
-     * @param string $relation_name possibly one of the keys in the relation_settings array
4975
-     * @return boolean
4976
-     */
4977
-    public function has_relation($relation_name)
4978
-    {
4979
-        $relations = $this->relation_settings();
4980
-        if (isset($relations[ $relation_name ])) {
4981
-            return true;
4982
-        }
4983
-        return false;
4984
-    }
4985
-
4986
-
4987
-    /**
4988
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4989
-     * Eg, on EE_Answer that would be ANS_ID field object
4990
-     *
4991
-     * @param $field_obj
4992
-     * @return boolean
4993
-     */
4994
-    public function is_primary_key_field($field_obj)
4995
-    {
4996
-        return $field_obj instanceof EE_Primary_Key_Field_Base;
4997
-    }
4998
-
4999
-
5000
-    /**
5001
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5002
-     * Eg, on EE_Answer that would be ANS_ID field object
5003
-     *
5004
-     * @return EE_Model_Field_Base
5005
-     * @throws EE_Error
5006
-     */
5007
-    public function get_primary_key_field()
5008
-    {
5009
-        if ($this->_primary_key_field === null) {
5010
-            foreach ($this->field_settings(true) as $field_obj) {
5011
-                if ($this->is_primary_key_field($field_obj)) {
5012
-                    $this->_primary_key_field = $field_obj;
5013
-                    break;
5014
-                }
5015
-            }
5016
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5017
-                throw new EE_Error(
5018
-                    sprintf(
5019
-                        esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5020
-                        get_class($this)
5021
-                    )
5022
-                );
5023
-            }
5024
-        }
5025
-        return $this->_primary_key_field;
5026
-    }
5027
-
5028
-
5029
-    /**
5030
-     * Returns whether or not not there is a primary key on this model.
5031
-     * Internally does some caching.
5032
-     *
5033
-     * @return boolean
5034
-     */
5035
-    public function has_primary_key_field()
5036
-    {
5037
-        if ($this->_has_primary_key_field === null) {
5038
-            try {
5039
-                $this->get_primary_key_field();
5040
-                $this->_has_primary_key_field = true;
5041
-            } catch (EE_Error $e) {
5042
-                $this->_has_primary_key_field = false;
5043
-            }
5044
-        }
5045
-        return $this->_has_primary_key_field;
5046
-    }
5047
-
5048
-
5049
-    /**
5050
-     * Finds the first field of type $field_class_name.
5051
-     *
5052
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5053
-     *                                 EE_Foreign_Key_Field, etc
5054
-     * @return EE_Model_Field_Base or null if none is found
5055
-     */
5056
-    public function get_a_field_of_type($field_class_name)
5057
-    {
5058
-        foreach ($this->field_settings() as $field) {
5059
-            if ($field instanceof $field_class_name) {
5060
-                return $field;
5061
-            }
5062
-        }
5063
-        return null;
5064
-    }
5065
-
5066
-
5067
-    /**
5068
-     * Gets a foreign key field pointing to model.
5069
-     *
5070
-     * @param string $model_name eg Event, Registration, not EEM_Event
5071
-     * @return EE_Foreign_Key_Field_Base
5072
-     * @throws EE_Error
5073
-     */
5074
-    public function get_foreign_key_to($model_name)
5075
-    {
5076
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5077
-            foreach ($this->field_settings() as $field) {
5078
-                if (
5079
-                    $field instanceof EE_Foreign_Key_Field_Base
5080
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5081
-                ) {
5082
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5083
-                    break;
5084
-                }
5085
-            }
5086
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5087
-                throw new EE_Error(
5088
-                    sprintf(
5089
-                        esc_html__(
5090
-                            "There is no foreign key field pointing to model %s on model %s",
5091
-                            'event_espresso'
5092
-                        ),
5093
-                        $model_name,
5094
-                        get_class($this)
5095
-                    )
5096
-                );
5097
-            }
5098
-        }
5099
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5100
-    }
5101
-
5102
-
5103
-    /**
5104
-     * Gets the table name (including $wpdb->prefix) for the table alias
5105
-     *
5106
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5107
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5108
-     *                            Either one works
5109
-     * @return string
5110
-     */
5111
-    public function get_table_for_alias($table_alias)
5112
-    {
5113
-        $table_alias_sans_model_relation_chain_prefix =
5114
-            EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5115
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5116
-    }
5117
-
5118
-
5119
-    /**
5120
-     * Returns a flat array of all field son this model, instead of organizing them
5121
-     * by table_alias as they are in the constructor.
5122
-     *
5123
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5124
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5125
-     */
5126
-    public function field_settings($include_db_only_fields = false)
5127
-    {
5128
-        if ($include_db_only_fields) {
5129
-            if ($this->_cached_fields === null) {
5130
-                $this->_cached_fields = [];
5131
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5132
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5133
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5134
-                    }
5135
-                }
5136
-            }
5137
-            return $this->_cached_fields;
5138
-        }
5139
-        if ($this->_cached_fields_non_db_only === null) {
5140
-            $this->_cached_fields_non_db_only = [];
5141
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5142
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5143
-                    /** @var $field_obj EE_Model_Field_Base */
5144
-                    if (! $field_obj->is_db_only_field()) {
5145
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5146
-                    }
5147
-                }
5148
-            }
5149
-        }
5150
-        return $this->_cached_fields_non_db_only;
5151
-    }
5152
-
5153
-
5154
-    /**
5155
-     *        cycle though array of attendees and create objects out of each item
5156
-     *
5157
-     * @access        private
5158
-     * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5159
-     * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5160
-     *                           numerically indexed)
5161
-     * @throws EE_Error
5162
-     * @throws ReflectionException
5163
-     */
5164
-    protected function _create_objects($rows = [])
5165
-    {
5166
-        $array_of_objects = [];
5167
-        if (empty($rows)) {
5168
-            return [];
5169
-        }
5170
-        $count_if_model_has_no_primary_key = 0;
5171
-        $has_primary_key                   = $this->has_primary_key_field();
5172
-        $primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5173
-        foreach ((array)$rows as $row) {
5174
-            if (empty($row)) {
5175
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5176
-                return [];
5177
-            }
5178
-            // check if we've already set this object in the results array,
5179
-            // in which case there's no need to process it further (again)
5180
-            if ($has_primary_key) {
5181
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5182
-                    $row,
5183
-                    $primary_key_field->get_qualified_column(),
5184
-                    $primary_key_field->get_table_column()
5185
-                );
5186
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5187
-                    continue;
5188
-                }
5189
-            }
5190
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5191
-            if (! $classInstance) {
5192
-                throw new EE_Error(
5193
-                    sprintf(
5194
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5195
-                        $this->get_this_model_name(),
5196
-                        http_build_query($row)
5197
-                    )
5198
-                );
5199
-            }
5200
-            // set the timezone on the instantiated objects
5201
-            $classInstance->set_timezone($this->_timezone);
5202
-            // make sure if there is any timezone setting present that we set the timezone for the object
5203
-            $key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5204
-            $array_of_objects[ $key ] = $classInstance;
5205
-            // also, for all the relations of type BelongsTo, see if we can cache
5206
-            // those related models
5207
-            // (we could do this for other relations too, but if there are conditions
5208
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5209
-            // so it requires a little more thought than just caching them immediately...)
5210
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5211
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5212
-                    // check if this model's INFO is present. If so, cache it on the model
5213
-                    $other_model           = $relation_obj->get_other_model();
5214
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5215
-                    // if we managed to make a model object from the results, cache it on the main model object
5216
-                    if ($other_model_obj_maybe) {
5217
-                        // set timezone on these other model objects if they are present
5218
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5219
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5220
-                    }
5221
-                }
5222
-            }
5223
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5224
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5225
-            // the field in the CustomSelects object
5226
-            if ($this->_custom_selections instanceof CustomSelects) {
5227
-                $classInstance->setCustomSelectsValues(
5228
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5229
-                );
5230
-            }
5231
-        }
5232
-        return $array_of_objects;
5233
-    }
5234
-
5235
-
5236
-    /**
5237
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5238
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5239
-     *
5240
-     * @param array $db_results_row
5241
-     * @return array
5242
-     */
5243
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5244
-    {
5245
-        $results = [];
5246
-        if ($this->_custom_selections instanceof CustomSelects) {
5247
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5248
-                if (isset($db_results_row[ $alias ])) {
5249
-                    $results[ $alias ] = $this->convertValueToDataType(
5250
-                        $db_results_row[ $alias ],
5251
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5252
-                    );
5253
-                }
5254
-            }
5255
-        }
5256
-        return $results;
5257
-    }
5258
-
5259
-
5260
-    /**
5261
-     * This will set the value for the given alias
5262
-     *
5263
-     * @param string $value
5264
-     * @param string $datatype (one of %d, %s, %f)
5265
-     * @return int|string|float (int for %d, string for %s, float for %f)
5266
-     */
5267
-    protected function convertValueToDataType($value, $datatype)
5268
-    {
5269
-        switch ($datatype) {
5270
-            case '%f':
5271
-                return (float)$value;
5272
-            case '%d':
5273
-                return (int)$value;
5274
-            default:
5275
-                return (string)$value;
5276
-        }
5277
-    }
5278
-
5279
-
5280
-    /**
5281
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5282
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5283
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5284
-     * object (as set in the model_field!).
5285
-     *
5286
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5287
-     * @throws EE_Error
5288
-     * @throws ReflectionException
5289
-     */
5290
-    public function create_default_object()
5291
-    {
5292
-        $this_model_fields_and_values = [];
5293
-        // setup the row using default values;
5294
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5295
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5296
-        }
5297
-        $className = $this->_get_class_name();
5298
-        return EE_Registry::instance()->load_class(
5299
-            $className,
5300
-            [$this_model_fields_and_values],
5301
-            false,
5302
-            false
5303
-        );
5304
-    }
5305
-
5306
-
5307
-    /**
5308
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5309
-     *                             or an stdClass where each property is the name of a column,
5310
-     * @return EE_Base_Class
5311
-     * @throws EE_Error
5312
-     * @throws ReflectionException
5313
-     */
5314
-    public function instantiate_class_from_array_or_object($cols_n_values)
5315
-    {
5316
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5317
-            $cols_n_values = get_object_vars($cols_n_values);
5318
-        }
5319
-        $primary_key = null;
5320
-        // make sure the array only has keys that are fields/columns on this model
5321
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5322
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5323
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5324
-        }
5325
-        $className = $this->_get_class_name();
5326
-        // check we actually found results that we can use to build our model object
5327
-        // if not, return null
5328
-        if ($this->has_primary_key_field()) {
5329
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5330
-                return null;
5331
-            }
5332
-        } elseif ($this->unique_indexes()) {
5333
-            $first_column = reset($this_model_fields_n_values);
5334
-            if (empty($first_column)) {
5335
-                return null;
5336
-            }
5337
-        }
5338
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5339
-        if ($primary_key) {
5340
-            $classInstance = $this->get_from_entity_map($primary_key);
5341
-            if (! $classInstance) {
5342
-                $classInstance = EE_Registry::instance()->load_class(
5343
-                    $className,
5344
-                    [$this_model_fields_n_values, $this->_timezone],
5345
-                    true,
5346
-                    false
5347
-                );
5348
-                // add this new object to the entity map
5349
-                $classInstance = $this->add_to_entity_map($classInstance);
5350
-            }
5351
-        } else {
5352
-            $classInstance = EE_Registry::instance()->load_class(
5353
-                $className,
5354
-                [$this_model_fields_n_values, $this->_timezone],
5355
-                true,
5356
-                false
5357
-            );
5358
-        }
5359
-        return $classInstance;
5360
-    }
5361
-
5362
-
5363
-    /**
5364
-     * Gets the model object from the  entity map if it exists
5365
-     *
5366
-     * @param int|string $id the ID of the model object
5367
-     * @return EE_Base_Class
5368
-     */
5369
-    public function get_from_entity_map($id)
5370
-    {
5371
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5372
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5373
-    }
5374
-
5375
-
5376
-    /**
5377
-     * add_to_entity_map
5378
-     * Adds the object to the model's entity mappings
5379
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5380
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5381
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5382
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5383
-     *        then this method should be called immediately after the update query
5384
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5385
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5386
-     *
5387
-     * @param EE_Base_Class $object
5388
-     * @return EE_Base_Class
5389
-     * @throws EE_Error
5390
-     * @throws ReflectionException
5391
-     */
5392
-    public function add_to_entity_map(EE_Base_Class $object)
5393
-    {
5394
-        $className = $this->_get_class_name();
5395
-        if (! $object instanceof $className) {
5396
-            throw new EE_Error(
5397
-                sprintf(
5398
-                    esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5399
-                    is_object($object) ? get_class($object) : $object,
5400
-                    $className
5401
-                )
5402
-            );
5403
-        }
5404
-        if (! $object->ID()) {
5405
-            throw new EE_Error(
5406
-                sprintf(
5407
-                    esc_html__(
5408
-                        "You tried storing a model object with NO ID in the %s entity mapper.",
5409
-                        "event_espresso"
5410
-                    ),
5411
-                    get_class($this)
5412
-                )
5413
-            );
5414
-        }
5415
-        // double check it's not already there
5416
-        $classInstance = $this->get_from_entity_map($object->ID());
5417
-        if ($classInstance) {
5418
-            return $classInstance;
5419
-        }
5420
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5421
-        return $object;
5422
-    }
5423
-
5424
-
5425
-    /**
5426
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5427
-     * if no identifier is provided, then the entire entity map is emptied
5428
-     *
5429
-     * @param int|string $id the ID of the model object
5430
-     * @return boolean
5431
-     */
5432
-    public function clear_entity_map($id = null)
5433
-    {
5434
-        if (empty($id)) {
5435
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5436
-            return true;
5437
-        }
5438
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5439
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5440
-            return true;
5441
-        }
5442
-        return false;
5443
-    }
5444
-
5445
-
5446
-    /**
5447
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5448
-     * Given an array where keys are column (or column alias) names and values,
5449
-     * returns an array of their corresponding field names and database values
5450
-     *
5451
-     * @param array $cols_n_values
5452
-     * @return array
5453
-     */
5454
-    public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5455
-    {
5456
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5457
-    }
5458
-
5459
-
5460
-    /**
5461
-     * _deduce_fields_n_values_from_cols_n_values
5462
-     * Given an array where keys are column (or column alias) names and values,
5463
-     * returns an array of their corresponding field names and database values
5464
-     *
5465
-     * @param array $cols_n_values
5466
-     * @return array
5467
-     */
5468
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5469
-    {
5470
-        $this_model_fields_n_values = [];
5471
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5472
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5473
-                $cols_n_values,
5474
-                $table_obj->get_fully_qualified_pk_column(),
5475
-                $table_obj->get_pk_column()
5476
-            );
5477
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5478
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5479
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5480
-                    if (! $field_obj->is_db_only_field()) {
5481
-                        // prepare field as if its coming from db
5482
-                        $prepared_value                            =
5483
-                            $field_obj->prepare_for_set($field_obj->get_default_value());
5484
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5485
-                    }
5486
-                }
5487
-            } else {
5488
-                // the table's rows existed. Use their values
5489
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5490
-                    if (! $field_obj->is_db_only_field()) {
5491
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5492
-                            $cols_n_values,
5493
-                            $field_obj->get_qualified_column(),
5494
-                            $field_obj->get_table_column()
5495
-                        );
5496
-                    }
5497
-                }
5498
-            }
5499
-        }
5500
-        return $this_model_fields_n_values;
5501
-    }
5502
-
5503
-
5504
-    /**
5505
-     * @param array  $cols_n_values
5506
-     * @param string $qualified_column
5507
-     * @param string $regular_column
5508
-     * @return null
5509
-     */
5510
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5511
-    {
5512
-        $value = null;
5513
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5514
-        // does the field on the model relate to this column retrieved from the db?
5515
-        // or is it a db-only field? (not relating to the model)
5516
-        if (isset($cols_n_values[ $qualified_column ])) {
5517
-            $value = $cols_n_values[ $qualified_column ];
5518
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5519
-            $value = $cols_n_values[ $regular_column ];
5520
-        } elseif (! empty($this->foreign_key_aliases)) {
5521
-            // no PK?  ok check if there is a foreign key alias set for this table
5522
-            // then check if that alias exists in the incoming data
5523
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5524
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5525
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5526
-                    $value = $cols_n_values[ $FK_alias ];
5527
-                    break;
5528
-                }
5529
-            }
5530
-        }
5531
-        return $value;
5532
-    }
5533
-
5534
-
5535
-    /**
5536
-     * refresh_entity_map_from_db
5537
-     * Makes sure the model object in the entity map at $id assumes the values
5538
-     * of the database (opposite of EE_base_Class::save())
5539
-     *
5540
-     * @param int|string $id
5541
-     * @return EE_Base_Class
5542
-     * @throws EE_Error
5543
-     * @throws ReflectionException
5544
-     */
5545
-    public function refresh_entity_map_from_db($id)
5546
-    {
5547
-        $obj_in_map = $this->get_from_entity_map($id);
5548
-        if ($obj_in_map) {
5549
-            $wpdb_results = $this->_get_all_wpdb_results(
5550
-                [[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5551
-            );
5552
-            if ($wpdb_results && is_array($wpdb_results)) {
5553
-                $one_row = reset($wpdb_results);
5554
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5555
-                    $obj_in_map->set_from_db($field_name, $db_value);
5556
-                }
5557
-                // clear the cache of related model objects
5558
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5559
-                    $obj_in_map->clear_cache($relation_name, null, true);
5560
-                }
5561
-            }
5562
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5563
-            return $obj_in_map;
5564
-        }
5565
-        return $this->get_one_by_ID($id);
5566
-    }
5567
-
5568
-
5569
-    /**
5570
-     * refresh_entity_map_with
5571
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5572
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5573
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5574
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5575
-     *
5576
-     * @param int|string    $id
5577
-     * @param EE_Base_Class $replacing_model_obj
5578
-     * @return EE_Base_Class
5579
-     * @throws EE_Error
5580
-     * @throws ReflectionException
5581
-     */
5582
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5583
-    {
5584
-        $obj_in_map = $this->get_from_entity_map($id);
5585
-        if ($obj_in_map) {
5586
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5587
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5588
-                    $obj_in_map->set($field_name, $value);
5589
-                }
5590
-                // make the model object in the entity map's cache match the $replacing_model_obj
5591
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5592
-                    $obj_in_map->clear_cache($relation_name, null, true);
5593
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5594
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5595
-                    }
5596
-                }
5597
-            }
5598
-            return $obj_in_map;
5599
-        }
5600
-        $this->add_to_entity_map($replacing_model_obj);
5601
-        return $replacing_model_obj;
5602
-    }
5603
-
5604
-
5605
-    /**
5606
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5607
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5608
-     * require_once($this->_getClassName().".class.php");
5609
-     *
5610
-     * @return string
5611
-     */
5612
-    private function _get_class_name()
5613
-    {
5614
-        return "EE_" . $this->get_this_model_name();
5615
-    }
5616
-
5617
-
5618
-    /**
5619
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5620
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5621
-     * it would be 'Events'.
5622
-     *
5623
-     * @param int $quantity
5624
-     * @return string
5625
-     */
5626
-    public function item_name($quantity = 1)
5627
-    {
5628
-        return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5629
-    }
5630
-
5631
-
5632
-    /**
5633
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5634
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5635
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5636
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5637
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5638
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5639
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5640
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5641
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5642
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5643
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5644
-     *        return $previousReturnValue.$returnString;
5645
-     * }
5646
-     * require('EEM_Answer.model.php');
5647
-     * $answer=EEM_Answer::instance();
5648
-     * echo $answer->my_callback('monkeys',100);
5649
-     * //will output "you called my_callback! and passed args:monkeys,100"
5650
-     *
5651
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5652
-     * @param array  $args       array of original arguments passed to the function
5653
-     * @return mixed whatever the plugin which calls add_filter decides
5654
-     * @throws EE_Error
5655
-     */
5656
-    public function __call($methodName, $args)
5657
-    {
5658
-        $className = get_class($this);
5659
-        $tagName   = "FHEE__{$className}__{$methodName}";
5660
-        if (! has_filter($tagName)) {
5661
-            throw new EE_Error(
5662
-                sprintf(
5663
-                    esc_html__(
5664
-                        '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 );',
5665
-                        'event_espresso'
5666
-                    ),
5667
-                    $methodName,
5668
-                    $className,
5669
-                    $tagName,
5670
-                    '<br />'
5671
-                )
5672
-            );
5673
-        }
5674
-        return apply_filters($tagName, null, $this, $args);
5675
-    }
5676
-
5677
-
5678
-    /**
5679
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5680
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5681
-     *
5682
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5683
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5684
-     *                                                       the object's class name
5685
-     *                                                       or object's ID
5686
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5687
-     *                                                       exists in the database. If it does not, we add it
5688
-     * @return EE_Base_Class
5689
-     * @throws EE_Error
5690
-     * @throws ReflectionException
5691
-     */
5692
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5693
-    {
5694
-        $className = $this->_get_class_name();
5695
-        if ($base_class_obj_or_id instanceof $className) {
5696
-            $model_object = $base_class_obj_or_id;
5697
-        } else {
5698
-            $primary_key_field = $this->get_primary_key_field();
5699
-            if (
5700
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5701
-                && (
5702
-                    is_int($base_class_obj_or_id)
5703
-                    || is_string($base_class_obj_or_id)
5704
-                )
5705
-            ) {
5706
-                // assume it's an ID.
5707
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5708
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5709
-            } elseif (
5710
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5711
-                && is_string($base_class_obj_or_id)
5712
-            ) {
5713
-                // assume its a string representation of the object
5714
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5715
-            } else {
5716
-                throw new EE_Error(
5717
-                    sprintf(
5718
-                        esc_html__(
5719
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5720
-                            'event_espresso'
5721
-                        ),
5722
-                        $base_class_obj_or_id,
5723
-                        $this->_get_class_name(),
5724
-                        print_r($base_class_obj_or_id, true)
5725
-                    )
5726
-                );
5727
-            }
5728
-        }
5729
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5730
-            $model_object->save();
5731
-        }
5732
-        return $model_object;
5733
-    }
5734
-
5735
-
5736
-    /**
5737
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5738
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5739
-     * returns it ID.
5740
-     *
5741
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5742
-     * @return int|string depending on the type of this model object's ID
5743
-     * @throws EE_Error
5744
-     * @throws ReflectionException
5745
-     */
5746
-    public function ensure_is_ID($base_class_obj_or_id)
5747
-    {
5748
-        $className = $this->_get_class_name();
5749
-        if ($base_class_obj_or_id instanceof $className) {
5750
-            $id = $base_class_obj_or_id->ID();
5751
-        } elseif (is_int($base_class_obj_or_id)) {
5752
-            // assume it's an ID
5753
-            $id = $base_class_obj_or_id;
5754
-        } elseif (is_string($base_class_obj_or_id)) {
5755
-            // assume its a string representation of the object
5756
-            $id = $base_class_obj_or_id;
5757
-        } else {
5758
-            throw new EE_Error(
5759
-                sprintf(
5760
-                    esc_html__(
5761
-                        "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5762
-                        'event_espresso'
5763
-                    ),
5764
-                    $base_class_obj_or_id,
5765
-                    $this->_get_class_name(),
5766
-                    print_r($base_class_obj_or_id, true)
5767
-                )
5768
-            );
5769
-        }
5770
-        return $id;
5771
-    }
5772
-
5773
-
5774
-    /**
5775
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5776
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5777
-     * been sanitized and converted into the appropriate domain.
5778
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5779
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5780
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5781
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5782
-     * $EVT = EEM_Event::instance(); $old_setting =
5783
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5784
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5785
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5786
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5787
-     *
5788
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5789
-     * @return void
5790
-     */
5791
-    public function assume_values_already_prepared_by_model_object(
5792
-        $values_already_prepared = self::not_prepared_by_model_object
5793
-    ) {
5794
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5795
-    }
5796
-
5797
-
5798
-    /**
5799
-     * Read comments for assume_values_already_prepared_by_model_object()
5800
-     *
5801
-     * @return int
5802
-     */
5803
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5804
-    {
5805
-        return $this->_values_already_prepared_by_model_object;
5806
-    }
5807
-
5808
-
5809
-    /**
5810
-     * Gets all the indexes on this model
5811
-     *
5812
-     * @return EE_Index[]
5813
-     */
5814
-    public function indexes()
5815
-    {
5816
-        return $this->_indexes;
5817
-    }
5818
-
5819
-
5820
-    /**
5821
-     * Gets all the Unique Indexes on this model
5822
-     *
5823
-     * @return EE_Unique_Index[]
5824
-     */
5825
-    public function unique_indexes()
5826
-    {
5827
-        $unique_indexes = [];
5828
-        foreach ($this->_indexes as $name => $index) {
5829
-            if ($index instanceof EE_Unique_Index) {
5830
-                $unique_indexes [ $name ] = $index;
5831
-            }
5832
-        }
5833
-        return $unique_indexes;
5834
-    }
5835
-
5836
-
5837
-    /**
5838
-     * Gets all the fields which, when combined, make the primary key.
5839
-     * This is usually just an array with 1 element (the primary key), but in cases
5840
-     * where there is no primary key, it's a combination of fields as defined
5841
-     * on a primary index
5842
-     *
5843
-     * @return EE_Model_Field_Base[] indexed by the field's name
5844
-     * @throws EE_Error
5845
-     */
5846
-    public function get_combined_primary_key_fields()
5847
-    {
5848
-        foreach ($this->indexes() as $index) {
5849
-            if ($index instanceof EE_Primary_Key_Index) {
5850
-                return $index->fields();
5851
-            }
5852
-        }
5853
-        return [$this->primary_key_name() => $this->get_primary_key_field()];
5854
-    }
5855
-
5856
-
5857
-    /**
5858
-     * Used to build a primary key string (when the model has no primary key),
5859
-     * which can be used a unique string to identify this model object.
5860
-     *
5861
-     * @param array $fields_n_values keys are field names, values are their values.
5862
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5863
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5864
-     *                               before passing it to this function (that will convert it from columns-n-values
5865
-     *                               to field-names-n-values).
5866
-     * @return string
5867
-     * @throws EE_Error
5868
-     */
5869
-    public function get_index_primary_key_string($fields_n_values)
5870
-    {
5871
-        $cols_n_values_for_primary_key_index = array_intersect_key(
5872
-            $fields_n_values,
5873
-            $this->get_combined_primary_key_fields()
5874
-        );
5875
-        return http_build_query($cols_n_values_for_primary_key_index);
5876
-    }
5877
-
5878
-
5879
-    /**
5880
-     * Gets the field values from the primary key string
5881
-     *
5882
-     * @param string $index_primary_key_string
5883
-     * @return null|array
5884
-     * @throws EE_Error
5885
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5886
-     */
5887
-    public function parse_index_primary_key_string($index_primary_key_string)
5888
-    {
5889
-        $key_fields = $this->get_combined_primary_key_fields();
5890
-        // check all of them are in the $id
5891
-        $key_values_in_combined_pk = [];
5892
-        parse_str($index_primary_key_string, $key_values_in_combined_pk);
5893
-        foreach ($key_fields as $key_field_name => $field_obj) {
5894
-            if (! isset($key_values_in_combined_pk[ $key_field_name ])) {
5895
-                return null;
5896
-            }
5897
-        }
5898
-        return $key_values_in_combined_pk;
5899
-    }
5900
-
5901
-
5902
-    /**
5903
-     * verifies that an array of key-value pairs for model fields has a key
5904
-     * for each field comprising the primary key index
5905
-     *
5906
-     * @param array $key_values
5907
-     * @return boolean
5908
-     * @throws EE_Error
5909
-     */
5910
-    public function has_all_combined_primary_key_fields($key_values)
5911
-    {
5912
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5913
-        foreach ($keys_it_should_have as $key) {
5914
-            if (! isset($key_values[ $key ])) {
5915
-                return false;
5916
-            }
5917
-        }
5918
-        return true;
5919
-    }
5920
-
5921
-
5922
-    /**
5923
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5924
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
5925
-     *
5926
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5927
-     * @param array               $query_params                     see github link below for more info
5928
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5929
-     * @throws EE_Error
5930
-     * @throws ReflectionException
5931
-     * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5932
-     *                                                              indexed)
5933
-     */
5934
-    public function get_all_copies($model_object_or_attributes_array, $query_params = [])
5935
-    {
5936
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5937
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
5938
-        } elseif (is_array($model_object_or_attributes_array)) {
5939
-            $attributes_array = $model_object_or_attributes_array;
5940
-        } else {
5941
-            throw new EE_Error(
5942
-                sprintf(
5943
-                    esc_html__(
5944
-                        "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5945
-                        "event_espresso"
5946
-                    ),
5947
-                    $model_object_or_attributes_array
5948
-                )
5949
-            );
5950
-        }
5951
-        // even copies obviously won't have the same ID, so remove the primary key
5952
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
5953
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5954
-            unset($attributes_array[ $this->primary_key_name() ]);
5955
-        }
5956
-        if (isset($query_params[0])) {
5957
-            $query_params[0] = array_merge($attributes_array, $query_params);
5958
-        } else {
5959
-            $query_params[0] = $attributes_array;
5960
-        }
5961
-        return $this->get_all($query_params);
5962
-    }
5963
-
5964
-
5965
-    /**
5966
-     * Gets the first copy we find. See get_all_copies for more details
5967
-     *
5968
-     * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
5969
-     * @param array $query_params
5970
-     * @return EE_Base_Class
5971
-     * @throws EE_Error
5972
-     * @throws ReflectionException
5973
-     */
5974
-    public function get_one_copy($model_object_or_attributes_array, $query_params = [])
5975
-    {
5976
-        if (! is_array($query_params)) {
5977
-            EE_Error::doing_it_wrong(
5978
-                'EEM_Base::get_one_copy',
5979
-                sprintf(
5980
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5981
-                    gettype($query_params)
5982
-                ),
5983
-                '4.6.0'
5984
-            );
5985
-            $query_params = [];
5986
-        }
5987
-        $query_params['limit'] = 1;
5988
-        $copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5989
-        if (is_array($copies)) {
5990
-            return array_shift($copies);
5991
-        }
5992
-        return null;
5993
-    }
5994
-
5995
-
5996
-    /**
5997
-     * Updates the item with the specified id. Ignores default query parameters because
5998
-     * we have specified the ID, and its assumed we KNOW what we're doing
5999
-     *
6000
-     * @param array      $fields_n_values keys are field names, values are their new values
6001
-     * @param int|string $id              the value of the primary key to update
6002
-     * @return int number of rows updated
6003
-     * @throws EE_Error
6004
-     * @throws ReflectionException
6005
-     */
6006
-    public function update_by_ID($fields_n_values, $id)
6007
-    {
6008
-        $query_params = [
6009
-            0                          => [$this->get_primary_key_field()->get_name() => $id],
6010
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6011
-        ];
6012
-        return $this->update($fields_n_values, $query_params);
6013
-    }
6014
-
6015
-
6016
-    /**
6017
-     * Changes an operator which was supplied to the models into one usable in SQL
6018
-     *
6019
-     * @param string $operator_supplied
6020
-     * @return string an operator which can be used in SQL
6021
-     * @throws EE_Error
6022
-     */
6023
-    private function _prepare_operator_for_sql($operator_supplied)
6024
-    {
6025
-        $sql_operator =
6026
-            isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6027
-                : null;
6028
-        if ($sql_operator) {
6029
-            return $sql_operator;
6030
-        }
6031
-        throw new EE_Error(
6032
-            sprintf(
6033
-                esc_html__(
6034
-                    "The operator '%s' is not in the list of valid operators: %s",
6035
-                    "event_espresso"
6036
-                ),
6037
-                $operator_supplied,
6038
-                implode(",", array_keys($this->_valid_operators))
6039
-            )
6040
-        );
6041
-    }
6042
-
6043
-
6044
-    /**
6045
-     * Gets the valid operators
6046
-     *
6047
-     * @return array keys are accepted strings, values are the SQL they are converted to
6048
-     */
6049
-    public function valid_operators()
6050
-    {
6051
-        return $this->_valid_operators;
6052
-    }
6053
-
6054
-
6055
-    /**
6056
-     * Gets the between-style operators (take 2 arguments).
6057
-     *
6058
-     * @return array keys are accepted strings, values are the SQL they are converted to
6059
-     */
6060
-    public function valid_between_style_operators()
6061
-    {
6062
-        return array_intersect(
6063
-            $this->valid_operators(),
6064
-            $this->_between_style_operators
6065
-        );
6066
-    }
6067
-
6068
-
6069
-    /**
6070
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6071
-     *
6072
-     * @return array keys are accepted strings, values are the SQL they are converted to
6073
-     */
6074
-    public function valid_like_style_operators()
6075
-    {
6076
-        return array_intersect(
6077
-            $this->valid_operators(),
6078
-            $this->_like_style_operators
6079
-        );
6080
-    }
6081
-
6082
-
6083
-    /**
6084
-     * Gets the "in"-style operators
6085
-     *
6086
-     * @return array keys are accepted strings, values are the SQL they are converted to
6087
-     */
6088
-    public function valid_in_style_operators()
6089
-    {
6090
-        return array_intersect(
6091
-            $this->valid_operators(),
6092
-            $this->_in_style_operators
6093
-        );
6094
-    }
6095
-
6096
-
6097
-    /**
6098
-     * Gets the "null"-style operators (accept no arguments)
6099
-     *
6100
-     * @return array keys are accepted strings, values are the SQL they are converted to
6101
-     */
6102
-    public function valid_null_style_operators()
6103
-    {
6104
-        return array_intersect(
6105
-            $this->valid_operators(),
6106
-            $this->_null_style_operators
6107
-        );
6108
-    }
6109
-
6110
-
6111
-    /**
6112
-     * Gets an array where keys are the primary keys and values are their 'names'
6113
-     * (as determined by the model object's name() function, which is often overridden)
6114
-     *
6115
-     * @param array $query_params like get_all's
6116
-     * @return string[]
6117
-     * @throws EE_Error
6118
-     * @throws ReflectionException
6119
-     */
6120
-    public function get_all_names($query_params = [])
6121
-    {
6122
-        $objs  = $this->get_all($query_params);
6123
-        $names = [];
6124
-        foreach ($objs as $obj) {
6125
-            $names[ $obj->ID() ] = $obj->name();
6126
-        }
6127
-        return $names;
6128
-    }
6129
-
6130
-
6131
-    /**
6132
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6133
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6134
-     * this is duplicated effort and reduces efficiency) you would be better to use
6135
-     * array_keys() on $model_objects.
6136
-     *
6137
-     * @param EE_Base_Class[] $model_objects
6138
-     * @param boolean         $filter_out_empty_ids  if a model object has an ID of '' or 0, don't bother including it
6139
-     *                                               in the returned array
6140
-     * @return array
6141
-     * @throws EE_Error
6142
-     * @throws ReflectionException
6143
-     */
6144
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6145
-    {
6146
-        if (! $this->has_primary_key_field()) {
6147
-            if (WP_DEBUG) {
6148
-                EE_Error::add_error(
6149
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6150
-                    __FILE__,
6151
-                    __FUNCTION__,
6152
-                    __LINE__
6153
-                );
6154
-            }
6155
-        }
6156
-        $IDs = [];
6157
-        foreach ($model_objects as $model_object) {
6158
-            $id = $model_object->ID();
6159
-            if (! $id) {
6160
-                if ($filter_out_empty_ids) {
6161
-                    continue;
6162
-                }
6163
-                if (WP_DEBUG) {
6164
-                    EE_Error::add_error(
6165
-                        esc_html__(
6166
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6167
-                            'event_espresso'
6168
-                        ),
6169
-                        __FILE__,
6170
-                        __FUNCTION__,
6171
-                        __LINE__
6172
-                    );
6173
-                }
6174
-            }
6175
-            $IDs[] = $id;
6176
-        }
6177
-        return $IDs;
6178
-    }
6179
-
6180
-
6181
-    /**
6182
-     * Returns the string used in capabilities relating to this model. If there
6183
-     * are no capabilities that relate to this model returns false
6184
-     *
6185
-     * @return string|false
6186
-     */
6187
-    public function cap_slug()
6188
-    {
6189
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6190
-    }
6191
-
6192
-
6193
-    /**
6194
-     * Returns the capability-restrictions array (@param string $context
6195
-     *
6196
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6197
-     * @throws EE_Error
6198
-     * @see EEM_Base::_cap_restrictions).
6199
-     *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6200
-     *      only returns the cap restrictions array in that context (ie, the array
6201
-     *      at that key)
6202
-     *
6203
-     */
6204
-    public function cap_restrictions($context = EEM_Base::caps_read)
6205
-    {
6206
-        EEM_Base::verify_is_valid_cap_context($context);
6207
-        // check if we ought to run the restriction generator first
6208
-        if (
6209
-            isset($this->_cap_restriction_generators[ $context ])
6210
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6211
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6212
-        ) {
6213
-            $this->_cap_restrictions[ $context ] = array_merge(
6214
-                $this->_cap_restrictions[ $context ],
6215
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6216
-            );
6217
-        }
6218
-        // and make sure we've finalized the construction of each restriction
6219
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6220
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6221
-                $where_conditions_obj->_finalize_construct($this);
6222
-            }
6223
-        }
6224
-        return $this->_cap_restrictions[ $context ];
6225
-    }
6226
-
6227
-
6228
-    /**
6229
-     * Indicating whether or not this model thinks its a wp core model
6230
-     *
6231
-     * @return boolean
6232
-     */
6233
-    public function is_wp_core_model()
6234
-    {
6235
-        return $this->_wp_core_model;
6236
-    }
6237
-
6238
-
6239
-    /**
6240
-     * Gets all the caps that are missing which impose a restriction on
6241
-     * queries made in this context
6242
-     *
6243
-     * @param string $context one of EEM_Base::caps_ constants
6244
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6245
-     * @throws EE_Error
6246
-     */
6247
-    public function caps_missing($context = EEM_Base::caps_read)
6248
-    {
6249
-        $missing_caps     = [];
6250
-        $cap_restrictions = $this->cap_restrictions($context);
6251
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6252
-            if (
6253
-            ! EE_Capabilities::instance()
6254
-                             ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6255
-            ) {
6256
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6257
-            }
6258
-        }
6259
-        return $missing_caps;
6260
-    }
6261
-
6262
-
6263
-    /**
6264
-     * Gets the mapping from capability contexts to action strings used in capability names
6265
-     *
6266
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6267
-     * one of 'read', 'edit', or 'delete'
6268
-     */
6269
-    public function cap_contexts_to_cap_action_map()
6270
-    {
6271
-        return apply_filters(
6272
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6273
-            $this->_cap_contexts_to_cap_action_map,
6274
-            $this
6275
-        );
6276
-    }
6277
-
6278
-
6279
-    /**
6280
-     * Gets the action string for the specified capability context
6281
-     *
6282
-     * @param string $context
6283
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6284
-     * @throws EE_Error
6285
-     */
6286
-    public function cap_action_for_context($context)
6287
-    {
6288
-        $mapping = $this->cap_contexts_to_cap_action_map();
6289
-        if (isset($mapping[ $context ])) {
6290
-            return $mapping[ $context ];
6291
-        }
6292
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6293
-            return $action;
6294
-        }
6295
-        throw new EE_Error(
6296
-            sprintf(
6297
-                esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6298
-                $context,
6299
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6300
-            )
6301
-        );
6302
-    }
6303
-
6304
-
6305
-    /**
6306
-     * Returns all the capability contexts which are valid when querying models
6307
-     *
6308
-     * @return array
6309
-     */
6310
-    public static function valid_cap_contexts()
6311
-    {
6312
-        return apply_filters(
6313
-            'FHEE__EEM_Base__valid_cap_contexts',
6314
-            [
6315
-                self::caps_read,
6316
-                self::caps_read_admin,
6317
-                self::caps_edit,
6318
-                self::caps_delete,
6319
-            ]
6320
-        );
6321
-    }
6322
-
6323
-
6324
-    /**
6325
-     * Returns all valid options for 'default_where_conditions'
6326
-     *
6327
-     * @return array
6328
-     */
6329
-    public static function valid_default_where_conditions()
6330
-    {
6331
-        return [
6332
-            EEM_Base::default_where_conditions_all,
6333
-            EEM_Base::default_where_conditions_this_only,
6334
-            EEM_Base::default_where_conditions_others_only,
6335
-            EEM_Base::default_where_conditions_minimum_all,
6336
-            EEM_Base::default_where_conditions_minimum_others,
6337
-            EEM_Base::default_where_conditions_none,
6338
-        ];
6339
-    }
6340
-
6341
-    // public static function default_where_conditions_full
6342
-
6343
-
6344
-    /**
6345
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6346
-     *
6347
-     * @param string $context
6348
-     * @return bool
6349
-     * @throws EE_Error
6350
-     */
6351
-    public static function verify_is_valid_cap_context($context)
6352
-    {
6353
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6354
-        if (in_array($context, $valid_cap_contexts)) {
6355
-            return true;
6356
-        }
6357
-        throw new EE_Error(
6358
-            sprintf(
6359
-                esc_html__(
6360
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6361
-                    'event_espresso'
6362
-                ),
6363
-                $context,
6364
-                'EEM_Base',
6365
-                implode(',', $valid_cap_contexts)
6366
-            )
6367
-        );
6368
-    }
6369
-
6370
-
6371
-    /**
6372
-     * Clears all the models field caches. This is only useful when a sub-class
6373
-     * might have added a field or something and these caches might be invalidated
6374
-     */
6375
-    protected function _invalidate_field_caches()
6376
-    {
6377
-        $this->_cache_foreign_key_to_fields = [];
6378
-        $this->_cached_fields               = null;
6379
-        $this->_cached_fields_non_db_only   = null;
6380
-    }
6381
-
6382
-
6383
-    /**
6384
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6385
-     * (eg "and", "or", "not").
6386
-     *
6387
-     * @return array
6388
-     */
6389
-    public function logic_query_param_keys()
6390
-    {
6391
-        return $this->_logic_query_param_keys;
6392
-    }
6393
-
6394
-
6395
-    /**
6396
-     * Determines whether or not the where query param array key is for a logic query param.
6397
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6398
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6399
-     *
6400
-     * @param $query_param_key
6401
-     * @return bool
6402
-     */
6403
-    public function is_logic_query_param_key($query_param_key)
6404
-    {
6405
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6406
-            if (
6407
-                $query_param_key === $logic_query_param_key
6408
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6409
-            ) {
6410
-                return true;
6411
-            }
6412
-        }
6413
-        return false;
6414
-    }
6415
-
6416
-
6417
-    /**
6418
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6419
-     *
6420
-     * @return boolean
6421
-     * @since 4.9.74.p
6422
-     */
6423
-    public function hasPassword()
6424
-    {
6425
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6426
-        if ($this->has_password_field === null) {
6427
-            $password_field           = $this->getPasswordField();
6428
-            $this->has_password_field = $password_field instanceof EE_Password_Field;
6429
-        }
6430
-        return $this->has_password_field;
6431
-    }
6432
-
6433
-
6434
-    /**
6435
-     * Returns the password field on this model, if there is one
6436
-     *
6437
-     * @return EE_Password_Field|null
6438
-     * @since 4.9.74.p
6439
-     */
6440
-    public function getPasswordField()
6441
-    {
6442
-        // if we definitely already know there is a password field or not (because has_password_field is true or false)
6443
-        // there's no need to search for it. If we don't know yet, then find out
6444
-        if ($this->has_password_field === null && $this->password_field === null) {
6445
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6446
-        }
6447
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6448
-        return $this->password_field;
6449
-    }
6450
-
6451
-
6452
-    /**
6453
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6454
-     *
6455
-     * @return EE_Model_Field_Base[]
6456
-     * @throws EE_Error
6457
-     * @since 4.9.74.p
6458
-     */
6459
-    public function getPasswordProtectedFields()
6460
-    {
6461
-        $password_field = $this->getPasswordField();
6462
-        $fields         = [];
6463
-        if ($password_field instanceof EE_Password_Field) {
6464
-            $field_names = $password_field->protectedFields();
6465
-            foreach ($field_names as $field_name) {
6466
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6467
-            }
6468
-        }
6469
-        return $fields;
6470
-    }
6471
-
6472
-
6473
-    /**
6474
-     * Checks if the current user can perform the requested action on this model
6475
-     *
6476
-     * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6477
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6478
-     * @return bool
6479
-     * @throws EE_Error
6480
-     * @throws InvalidArgumentException
6481
-     * @throws InvalidDataTypeException
6482
-     * @throws InvalidInterfaceException
6483
-     * @throws ReflectionException
6484
-     * @throws UnexpectedEntityException
6485
-     * @since 4.9.74.p
6486
-     */
6487
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6488
-    {
6489
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6490
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6491
-        }
6492
-        if (! is_array($model_obj_or_fields_n_values)) {
6493
-            throw new UnexpectedEntityException(
6494
-                $model_obj_or_fields_n_values,
6495
-                'EE_Base_Class',
6496
-                sprintf(
6497
-                    esc_html__(
6498
-                        '%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6499
-                        'event_espresso'
6500
-                    ),
6501
-                    __FUNCTION__
6502
-                )
6503
-            );
6504
-        }
6505
-        return $this->exists(
6506
-            $this->alter_query_params_to_restrict_by_ID(
6507
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6508
-                [
6509
-                    'default_where_conditions' => 'none',
6510
-                    'caps'                     => $cap_to_check,
6511
-                ]
6512
-            )
6513
-        );
6514
-    }
6515
-
6516
-
6517
-    /**
6518
-     * Returns the query param where conditions key to the password affecting this model.
6519
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6520
-     *
6521
-     * @return null|string
6522
-     * @throws EE_Error
6523
-     * @throws InvalidArgumentException
6524
-     * @throws InvalidDataTypeException
6525
-     * @throws InvalidInterfaceException
6526
-     * @throws ModelConfigurationException
6527
-     * @throws ReflectionException
6528
-     * @since 4.9.74.p
6529
-     */
6530
-    public function modelChainAndPassword()
6531
-    {
6532
-        if ($this->model_chain_to_password === null) {
6533
-            throw new ModelConfigurationException(
6534
-                $this,
6535
-                esc_html_x(
6536
-                // @codingStandardsIgnoreStart
6537
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6538
-                    // @codingStandardsIgnoreEnd
6539
-                    '1: model name',
6540
-                    'event_espresso'
6541
-                )
6542
-            );
6543
-        }
6544
-        if ($this->model_chain_to_password === '') {
6545
-            $model_with_password = $this;
6546
-        } else {
6547
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6548
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6549
-            } else {
6550
-                $last_model_in_chain = $this->model_chain_to_password;
6551
-            }
6552
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6553
-        }
6554
-
6555
-        $password_field = $model_with_password->getPasswordField();
6556
-        if ($password_field instanceof EE_Password_Field) {
6557
-            $password_field_name = $password_field->get_name();
6558
-        } else {
6559
-            throw new ModelConfigurationException(
6560
-                $this,
6561
-                sprintf(
6562
-                    esc_html_x(
6563
-                        '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"',
6564
-                        '1: model name, 2: special string',
6565
-                        'event_espresso'
6566
-                    ),
6567
-                    $model_with_password->get_this_model_name(),
6568
-                    $this->model_chain_to_password
6569
-                )
6570
-            );
6571
-        }
6572
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6573
-    }
6574
-
6575
-
6576
-    /**
6577
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6578
-     * or if this model itself has a password affecting access to some of its other fields.
6579
-     *
6580
-     * @return boolean
6581
-     * @since 4.9.74.p
6582
-     */
6583
-    public function restrictedByRelatedModelPassword()
6584
-    {
6585
-        return $this->model_chain_to_password !== null;
6586
-    }
3865
+		}
3866
+		return $null_friendly_where_conditions;
3867
+	}
3868
+
3869
+
3870
+	/**
3871
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3872
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3873
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3874
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3875
+	 *
3876
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3877
+	 * @return array
3878
+	 * @throws EE_Error
3879
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3880
+	 */
3881
+	private function _get_default_where_conditions($model_relation_path = '')
3882
+	{
3883
+		if ($this->_ignore_where_strategy) {
3884
+			return [];
3885
+		}
3886
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3887
+	}
3888
+
3889
+
3890
+	/**
3891
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3892
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3893
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3894
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3895
+	 * Similar to _get_default_where_conditions
3896
+	 *
3897
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3898
+	 * @return array
3899
+	 * @throws EE_Error
3900
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3901
+	 */
3902
+	protected function _get_minimum_where_conditions($model_relation_path = '')
3903
+	{
3904
+		if ($this->_ignore_where_strategy) {
3905
+			return [];
3906
+		}
3907
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3908
+	}
3909
+
3910
+
3911
+	/**
3912
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3913
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3914
+	 *
3915
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
3916
+	 * @return string
3917
+	 * @throws EE_Error
3918
+	 */
3919
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3920
+	{
3921
+		$selects = $this->_get_columns_to_select_for_this_model();
3922
+		foreach (
3923
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3924
+		) {
3925
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3926
+			$other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3927
+			foreach ($other_model_selects as $key => $value) {
3928
+				$selects[] = $value;
3929
+			}
3930
+		}
3931
+		return implode(", ", $selects);
3932
+	}
3933
+
3934
+
3935
+	/**
3936
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3937
+	 * So that's going to be the columns for all the fields on the model
3938
+	 *
3939
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
3940
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3941
+	 */
3942
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3943
+	{
3944
+		$fields                                       = $this->field_settings();
3945
+		$selects                                      = [];
3946
+		$table_alias_with_model_relation_chain_prefix =
3947
+			EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3948
+				$model_relation_chain,
3949
+				$this->get_this_model_name()
3950
+			);
3951
+		foreach ($fields as $field_obj) {
3952
+			$selects[] = $table_alias_with_model_relation_chain_prefix
3953
+						 . $field_obj->get_table_alias()
3954
+						 . "."
3955
+						 . $field_obj->get_table_column()
3956
+						 . " AS '"
3957
+						 . $table_alias_with_model_relation_chain_prefix
3958
+						 . $field_obj->get_table_alias()
3959
+						 . "."
3960
+						 . $field_obj->get_table_column()
3961
+						 . "'";
3962
+		}
3963
+		// make sure we are also getting the PKs of each table
3964
+		$tables = $this->get_tables();
3965
+		if (count($tables) > 1) {
3966
+			foreach ($tables as $table_obj) {
3967
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3968
+									   . $table_obj->get_fully_qualified_pk_column();
3969
+				if (! in_array($qualified_pk_column, $selects)) {
3970
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3971
+				}
3972
+			}
3973
+		}
3974
+		return $selects;
3975
+	}
3976
+
3977
+
3978
+	/**
3979
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3980
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3981
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3982
+	 * SQL for joining, and the data types
3983
+	 *
3984
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
3985
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3986
+	 * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
3987
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3988
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
3989
+	 *                                                          or 'Registration's
3990
+	 * @param string|null                 $original_query_param what it originally was (eg
3991
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3992
+	 *                                                          matches $query_param
3993
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3994
+	 * @throws EE_Error
3995
+	 */
3996
+	private function _extract_related_model_info_from_query_param(
3997
+		$query_param,
3998
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
3999
+		$query_param_type,
4000
+		$original_query_param = null
4001
+	) {
4002
+		if ($original_query_param === null) {
4003
+			$original_query_param = $query_param;
4004
+		}
4005
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4006
+		// whether or not to allow logic_query_params like 'NOT','OR', or 'AND'
4007
+		$allow_logic_query_params = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4008
+		$allow_fields             = in_array(
4009
+			$query_param_type,
4010
+			['where', 'having', 'order_by', 'group_by', 'order', 'custom_selects', 0],
4011
+			true
4012
+		);
4013
+		// check to see if we have a field on this model
4014
+		$this_model_fields = $this->field_settings(true);
4015
+		if (array_key_exists($query_param, $this_model_fields)) {
4016
+			if ($allow_fields) {
4017
+				return;
4018
+			}
4019
+			throw new EE_Error(
4020
+				sprintf(
4021
+					esc_html__(
4022
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4023
+						"event_espresso"
4024
+					),
4025
+					$query_param,
4026
+					get_class($this),
4027
+					$query_param_type,
4028
+					$original_query_param
4029
+				)
4030
+			);
4031
+		}
4032
+		// check if this is a special logic query param
4033
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4034
+			if ($allow_logic_query_params) {
4035
+				return;
4036
+			}
4037
+			throw new EE_Error(
4038
+				sprintf(
4039
+					esc_html__(
4040
+						'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',
4041
+						'event_espresso'
4042
+					),
4043
+					implode('", "', $this->_logic_query_param_keys),
4044
+					$query_param,
4045
+					get_class($this),
4046
+					'<br />',
4047
+					"\t"
4048
+					. ' $passed_in_query_info = <pre>'
4049
+					. print_r($passed_in_query_info, true)
4050
+					. '</pre>'
4051
+					. "\n\t"
4052
+					. ' $query_param_type = '
4053
+					. $query_param_type
4054
+					. "\n\t"
4055
+					. ' $original_query_param = '
4056
+					. $original_query_param
4057
+				)
4058
+			);
4059
+		}
4060
+		// check if it's a custom selection
4061
+		if (
4062
+			$this->_custom_selections instanceof CustomSelects
4063
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4064
+		) {
4065
+			return;
4066
+		}
4067
+		// check if has a model name at the beginning
4068
+		// and
4069
+		// check if it's a field on a related model
4070
+		if (
4071
+		$this->extractJoinModelFromQueryParams(
4072
+			$passed_in_query_info,
4073
+			$query_param,
4074
+			$original_query_param,
4075
+			$query_param_type
4076
+		)
4077
+		) {
4078
+			return;
4079
+		}
4080
+
4081
+		// ok so $query_param didn't start with a model name
4082
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4083
+		// it's wack, that's what it is
4084
+		throw new EE_Error(
4085
+			sprintf(
4086
+				esc_html__(
4087
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4088
+					"event_espresso"
4089
+				),
4090
+				$query_param,
4091
+				get_class($this),
4092
+				$query_param_type,
4093
+				$original_query_param
4094
+			)
4095
+		);
4096
+	}
4097
+
4098
+
4099
+	/**
4100
+	 * Extracts any possible join model information from the provided possible_join_string.
4101
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model
4102
+	 * join
4103
+	 * parts that should be added to the query.
4104
+	 *
4105
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4106
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4107
+	 * @param null|string                 $original_query_param
4108
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4109
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4110
+	 *                                                           etc.)
4111
+	 * @return bool  returns true if a join was added and false if not.
4112
+	 * @throws EE_Error
4113
+	 */
4114
+	private function extractJoinModelFromQueryParams(
4115
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4116
+		$possible_join_string,
4117
+		$original_query_param,
4118
+		$query_parameter_type
4119
+	) {
4120
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4121
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4122
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4123
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4124
+				if ($possible_join_string === '') {
4125
+					// nothing left to $query_param
4126
+					// we should actually end in a field name, not a model like this!
4127
+					throw new EE_Error(
4128
+						sprintf(
4129
+							esc_html__(
4130
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4131
+								"event_espresso"
4132
+							),
4133
+							$possible_join_string,
4134
+							$query_parameter_type,
4135
+							get_class($this),
4136
+							$valid_related_model_name
4137
+						)
4138
+					);
4139
+				}
4140
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4141
+				$related_model_obj->_extract_related_model_info_from_query_param(
4142
+					$possible_join_string,
4143
+					$query_info_carrier,
4144
+					$query_parameter_type,
4145
+					$original_query_param
4146
+				);
4147
+				return true;
4148
+			}
4149
+			if ($possible_join_string === $valid_related_model_name) {
4150
+				$this->_add_join_to_model(
4151
+					$valid_related_model_name,
4152
+					$query_info_carrier,
4153
+					$original_query_param
4154
+				);
4155
+				return true;
4156
+			}
4157
+		}
4158
+		return false;
4159
+	}
4160
+
4161
+
4162
+	/**
4163
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4164
+	 *
4165
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4166
+	 * @throws EE_Error
4167
+	 */
4168
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4169
+	{
4170
+		if (
4171
+			$this->_custom_selections instanceof CustomSelects
4172
+			&& ($this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4173
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4174
+			)
4175
+		) {
4176
+			$original_selects = $this->_custom_selections->originalSelects();
4177
+			foreach ($original_selects as $alias => $select_configuration) {
4178
+				$this->extractJoinModelFromQueryParams(
4179
+					$query_info_carrier,
4180
+					$select_configuration[0],
4181
+					$select_configuration[0],
4182
+					'custom_selects'
4183
+				);
4184
+			}
4185
+		}
4186
+	}
4187
+
4188
+
4189
+	/**
4190
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4191
+	 * and store it on $passed_in_query_info
4192
+	 *
4193
+	 * @param string                      $model_name
4194
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4195
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4196
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4197
+	 *                                                          and are adding a join to 'Payment' with the original
4198
+	 *                                                          query param key
4199
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4200
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4201
+	 *                                                          Payment wants to add default query params so that it
4202
+	 *                                                          will know what models to prepend onto its default query
4203
+	 *                                                          params or in case it wants to rename tables (in case
4204
+	 *                                                          there are multiple joins to the same table)
4205
+	 * @return void
4206
+	 * @throws EE_Error
4207
+	 */
4208
+	private function _add_join_to_model(
4209
+		$model_name,
4210
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4211
+		$original_query_param
4212
+	) {
4213
+		$relation_obj         = $this->related_settings_for($model_name);
4214
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4215
+		// check if the relation is HABTM, because then we're essentially doing two joins
4216
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4217
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4218
+			$join_model_obj = $relation_obj->get_join_model();
4219
+			// replace the model specified with the join model for this relation chain, whi
4220
+			$relation_chain_to_join_model =
4221
+				EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4222
+					$model_name,
4223
+					$join_model_obj->get_this_model_name(),
4224
+					$model_relation_chain
4225
+				);
4226
+			$passed_in_query_info->merge(
4227
+				new EE_Model_Query_Info_Carrier(
4228
+					[$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4229
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4230
+				)
4231
+			);
4232
+		}
4233
+		// now just join to the other table pointed to by the relation object, and add its data types
4234
+		$passed_in_query_info->merge(
4235
+			new EE_Model_Query_Info_Carrier(
4236
+				[$model_relation_chain => $model_name],
4237
+				$relation_obj->get_join_statement($model_relation_chain)
4238
+			)
4239
+		);
4240
+	}
4241
+
4242
+
4243
+	/**
4244
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4245
+	 *
4246
+	 * @param array $where_params
4247
+	 * @return string of SQL
4248
+	 * @throws EE_Error
4249
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4250
+	 */
4251
+	private function _construct_where_clause($where_params)
4252
+	{
4253
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4254
+		if ($SQL) {
4255
+			return " WHERE " . $SQL;
4256
+		}
4257
+		return '';
4258
+	}
4259
+
4260
+
4261
+	/**
4262
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4263
+	 * and should be passed HAVING parameters, not WHERE parameters
4264
+	 *
4265
+	 * @param array $having_params
4266
+	 * @return string
4267
+	 * @throws EE_Error
4268
+	 */
4269
+	private function _construct_having_clause($having_params)
4270
+	{
4271
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4272
+		if ($SQL) {
4273
+			return " HAVING " . $SQL;
4274
+		}
4275
+		return '';
4276
+	}
4277
+
4278
+
4279
+	/**
4280
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4281
+	 * Event_Meta.meta_value = 'foo'))"
4282
+	 *
4283
+	 * @param array  $where_params
4284
+	 * @param string $glue joins each sub-clause together. Should really only be " AND " or " OR "...
4285
+	 * @return string of SQL
4286
+	 * @throws EE_Error
4287
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4288
+	 */
4289
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4290
+	{
4291
+		$where_clauses = [];
4292
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4293
+			$query_param =
4294
+				$this->_remove_stars_and_anything_after_from_condition_query_param_key(
4295
+					$query_param
4296
+				);// str_replace("*",'',$query_param);
4297
+			if (in_array($query_param, $this->_logic_query_param_keys)) {
4298
+				switch ($query_param) {
4299
+					case 'not':
4300
+					case 'NOT':
4301
+						$where_clauses[] = "! ("
4302
+										   . $this->_construct_condition_clause_recursive(
4303
+								$op_and_value_or_sub_condition,
4304
+								$glue
4305
+							)
4306
+										   . ")";
4307
+						break;
4308
+					case 'and':
4309
+					case 'AND':
4310
+						$where_clauses[] = " ("
4311
+										   . $this->_construct_condition_clause_recursive(
4312
+								$op_and_value_or_sub_condition,
4313
+								' AND '
4314
+							)
4315
+										   . ")";
4316
+						break;
4317
+					case 'or':
4318
+					case 'OR':
4319
+						$where_clauses[] = " ("
4320
+										   . $this->_construct_condition_clause_recursive(
4321
+								$op_and_value_or_sub_condition,
4322
+								' OR '
4323
+							)
4324
+										   . ")";
4325
+						break;
4326
+				}
4327
+			} else {
4328
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4329
+				// if it's not a normal field, maybe it's a custom selection?
4330
+				if (! $field_obj) {
4331
+					if ($this->_custom_selections instanceof CustomSelects) {
4332
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4333
+					} else {
4334
+						throw new EE_Error(
4335
+							sprintf(
4336
+								esc_html__(
4337
+									"%s is neither a valid model field name, nor a custom selection",
4338
+									"event_espresso"
4339
+								),
4340
+								$query_param
4341
+							)
4342
+						);
4343
+					}
4344
+				}
4345
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4346
+				$where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4347
+			}
4348
+		}
4349
+		return $where_clauses ? implode($glue, $where_clauses) : '';
4350
+	}
4351
+
4352
+
4353
+	/**
4354
+	 * Takes the input parameter and extract the table name (alias) and column name
4355
+	 *
4356
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4357
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4358
+	 * @throws EE_Error
4359
+	 */
4360
+	private function _deduce_column_name_from_query_param($query_param)
4361
+	{
4362
+		$field = $this->_deduce_field_from_query_param($query_param);
4363
+		if ($field) {
4364
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4365
+				$field->get_model_name(),
4366
+				$query_param
4367
+			);
4368
+			return $table_alias_prefix . $field->get_qualified_column();
4369
+		}
4370
+		if (
4371
+			$this->_custom_selections instanceof CustomSelects
4372
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4373
+		) {
4374
+			// maybe it's custom selection item?
4375
+			// if so, just use it as the "column name"
4376
+			return $query_param;
4377
+		}
4378
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4379
+			? implode(',', $this->_custom_selections->columnAliases())
4380
+			: '';
4381
+		throw new EE_Error(
4382
+			sprintf(
4383
+				esc_html__(
4384
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4385
+					"event_espresso"
4386
+				),
4387
+				$query_param,
4388
+				$custom_select_aliases
4389
+			)
4390
+		);
4391
+	}
4392
+
4393
+
4394
+	/**
4395
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4396
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4397
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4398
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4399
+	 *
4400
+	 * @param string $condition_query_param_key
4401
+	 * @return string
4402
+	 */
4403
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4404
+	{
4405
+		$pos_of_star = strpos($condition_query_param_key, '*');
4406
+		if ($pos_of_star === false) {
4407
+			return $condition_query_param_key;
4408
+		}
4409
+		return substr($condition_query_param_key, 0, $pos_of_star);
4410
+	}
4411
+
4412
+
4413
+	/**
4414
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4415
+	 *
4416
+	 * @param mixed      array | string    $op_and_value
4417
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4418
+	 * @return string
4419
+	 * @throws EE_Error
4420
+	 */
4421
+	private function _construct_op_and_value($op_and_value, $field_obj)
4422
+	{
4423
+		if (is_array($op_and_value)) {
4424
+			$operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4425
+			if (! $operator) {
4426
+				$php_array_like_string = [];
4427
+				foreach ($op_and_value as $key => $value) {
4428
+					$php_array_like_string[] = "$key=>$value";
4429
+				}
4430
+				throw new EE_Error(
4431
+					sprintf(
4432
+						esc_html__(
4433
+							"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))",
4434
+							"event_espresso"
4435
+						),
4436
+						implode(",", $php_array_like_string)
4437
+					)
4438
+				);
4439
+			}
4440
+			$value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4441
+		} else {
4442
+			$operator = '=';
4443
+			$value    = $op_and_value;
4444
+		}
4445
+		// check to see if the value is actually another field
4446
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4447
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4448
+		}
4449
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4450
+			// in this case, the value should be an array, or at least a comma-separated list
4451
+			// it will need to handle a little differently
4452
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4453
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4454
+			return $operator . SP . $cleaned_value;
4455
+		}
4456
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4457
+			// the value should be an array with count of two.
4458
+			if (count($value) !== 2) {
4459
+				throw new EE_Error(
4460
+					sprintf(
4461
+						esc_html__(
4462
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4463
+							'event_espresso'
4464
+						),
4465
+						"BETWEEN"
4466
+					)
4467
+				);
4468
+			}
4469
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4470
+			return $operator . SP . $cleaned_value;
4471
+		}
4472
+		if (in_array($operator, $this->valid_null_style_operators())) {
4473
+			if ($value !== null) {
4474
+				throw new EE_Error(
4475
+					sprintf(
4476
+						esc_html__(
4477
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4478
+							"event_espresso"
4479
+						),
4480
+						$value,
4481
+						$operator
4482
+					)
4483
+				);
4484
+			}
4485
+			return $operator;
4486
+		}
4487
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4488
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4489
+			// remove other junk. So just treat it as a string.
4490
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4491
+		}
4492
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4493
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4494
+		}
4495
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4496
+			throw new EE_Error(
4497
+				sprintf(
4498
+					esc_html__(
4499
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4500
+						'event_espresso'
4501
+					),
4502
+					$operator,
4503
+					$operator
4504
+				)
4505
+			);
4506
+		}
4507
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4508
+			throw new EE_Error(
4509
+				sprintf(
4510
+					esc_html__(
4511
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4512
+						'event_espresso'
4513
+					),
4514
+					$operator,
4515
+					$operator
4516
+				)
4517
+			);
4518
+		}
4519
+		throw new EE_Error(
4520
+			sprintf(
4521
+				esc_html__(
4522
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4523
+					"event_espresso"
4524
+				),
4525
+				http_build_query($op_and_value)
4526
+			)
4527
+		);
4528
+	}
4529
+
4530
+
4531
+	/**
4532
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4533
+	 *
4534
+	 * @param array                      $values
4535
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4536
+	 *                                              '%s'
4537
+	 * @return string
4538
+	 * @throws EE_Error
4539
+	 */
4540
+	public function _construct_between_value($values, $field_obj)
4541
+	{
4542
+		$cleaned_values = [];
4543
+		foreach ($values as $value) {
4544
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4545
+		}
4546
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4547
+	}
4548
+
4549
+
4550
+	/**
4551
+	 * Takes an array or a comma-separated list of $values and cleans them
4552
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4553
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4554
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4555
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4556
+	 *
4557
+	 * @param mixed                      $values    array or comma-separated string
4558
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4559
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4560
+	 * @throws EE_Error
4561
+	 */
4562
+	public function _construct_in_value($values, $field_obj)
4563
+	{
4564
+		// check if the value is a CSV list
4565
+		if (is_string($values)) {
4566
+			// in which case, turn it into an array
4567
+			$values = explode(",", $values);
4568
+		}
4569
+		$cleaned_values = [];
4570
+		foreach ($values as $value) {
4571
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4572
+		}
4573
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4574
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4575
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4576
+		if (empty($cleaned_values)) {
4577
+			$all_fields       = $this->field_settings();
4578
+			$a_field          = array_shift($all_fields);
4579
+			$main_table       = $this->_get_main_table();
4580
+			$cleaned_values[] = "SELECT "
4581
+								. $a_field->get_table_column()
4582
+								. " FROM "
4583
+								. $main_table->get_table_name()
4584
+								. " WHERE FALSE";
4585
+		}
4586
+		return "(" . implode(",", $cleaned_values) . ")";
4587
+	}
4588
+
4589
+
4590
+	/**
4591
+	 * @param mixed                      $value
4592
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4593
+	 * @return false|null|string
4594
+	 * @throws EE_Error
4595
+	 */
4596
+	private function _wpdb_prepare_using_field($value, $field_obj)
4597
+	{
4598
+		global $wpdb;
4599
+		if ($field_obj instanceof EE_Model_Field_Base) {
4600
+			return $wpdb->prepare(
4601
+				$field_obj->get_wpdb_data_type(),
4602
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4603
+			);
4604
+		} //$field_obj should really just be a data type
4605
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4606
+			throw new EE_Error(
4607
+				sprintf(
4608
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4609
+					$field_obj,
4610
+					implode(",", $this->_valid_wpdb_data_types)
4611
+				)
4612
+			);
4613
+		}
4614
+		return $wpdb->prepare($field_obj, $value);
4615
+	}
4616
+
4617
+
4618
+	/**
4619
+	 * Takes the input parameter and finds the model field that it indicates.
4620
+	 *
4621
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4622
+	 * @return EE_Model_Field_Base
4623
+	 * @throws EE_Error
4624
+	 */
4625
+	protected function _deduce_field_from_query_param($query_param_name)
4626
+	{
4627
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4628
+		// which will help us find the database table and column
4629
+		$query_param_parts = explode(".", $query_param_name);
4630
+		if (empty($query_param_parts)) {
4631
+			throw new EE_Error(
4632
+				sprintf(
4633
+					esc_html__(
4634
+						"_extract_column_name is empty when trying to extract column and table name from %s",
4635
+						'event_espresso'
4636
+					),
4637
+					$query_param_name
4638
+				)
4639
+			);
4640
+		}
4641
+		$number_of_parts       = count($query_param_parts);
4642
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4643
+		if ($number_of_parts === 1) {
4644
+			$field_name = $last_query_param_part;
4645
+			$model_obj  = $this;
4646
+		} else {// $number_of_parts >= 2
4647
+			// the last part is the column name, and there are only 2parts. therefore...
4648
+			$field_name = $last_query_param_part;
4649
+			$model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4650
+		}
4651
+		try {
4652
+			return $model_obj->field_settings_for($field_name);
4653
+		} catch (EE_Error $e) {
4654
+			return null;
4655
+		}
4656
+	}
4657
+
4658
+
4659
+	/**
4660
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4661
+	 * alias and column which corresponds to it
4662
+	 *
4663
+	 * @param string $field_name
4664
+	 * @return string
4665
+	 * @throws EE_Error
4666
+	 */
4667
+	public function _get_qualified_column_for_field($field_name)
4668
+	{
4669
+		$all_fields = $this->field_settings();
4670
+		$field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4671
+		if ($field) {
4672
+			return $field->get_qualified_column();
4673
+		}
4674
+		throw new EE_Error(
4675
+			sprintf(
4676
+				esc_html__(
4677
+					"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.",
4678
+					'event_espresso'
4679
+				),
4680
+				$field_name,
4681
+				get_class($this)
4682
+			)
4683
+		);
4684
+	}
4685
+
4686
+
4687
+	/**
4688
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4689
+	 * Example usage:
4690
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4691
+	 *      array(),
4692
+	 *      ARRAY_A,
4693
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4694
+	 *  );
4695
+	 * is equivalent to
4696
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4697
+	 * and
4698
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4699
+	 *      array(
4700
+	 *          array(
4701
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4702
+	 *          ),
4703
+	 *          ARRAY_A,
4704
+	 *          implode(
4705
+	 *              ', ',
4706
+	 *              array_merge(
4707
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4708
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4709
+	 *              )
4710
+	 *          )
4711
+	 *      )
4712
+	 *  );
4713
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4714
+	 *
4715
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4716
+	 *                                            and the one whose fields you are selecting for example: when querying
4717
+	 *                                            tickets model and selecting fields from the tickets model you would
4718
+	 *                                            leave this parameter empty, because no models are needed to join
4719
+	 *                                            between the queried model and the selected one. Likewise when
4720
+	 *                                            querying the datetime model and selecting fields from the tickets
4721
+	 *                                            model, it would also be left empty, because there is a direct
4722
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4723
+	 *                                            them together. However, when querying from the event model and
4724
+	 *                                            selecting fields from the ticket model, you should provide the string
4725
+	 *                                            'Datetime', indicating that the event model must first join to the
4726
+	 *                                            datetime model in order to find its relation to ticket model.
4727
+	 *                                            Also, when querying from the venue model and selecting fields from
4728
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4729
+	 *                                            indicating you need to join the venue model to the event model,
4730
+	 *                                            to the datetime model, in order to find its relation to the ticket
4731
+	 *                                            model. This string is used to deduce the prefix that gets added onto
4732
+	 *                                            the models' tables qualified columns
4733
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4734
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4735
+	 *                                            qualified column names
4736
+	 * @return array|string
4737
+	 */
4738
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4739
+	{
4740
+		$table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4741
+		$qualified_columns = [];
4742
+		foreach ($this->field_settings() as $field_name => $field) {
4743
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4744
+		}
4745
+		return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4746
+	}
4747
+
4748
+
4749
+	/**
4750
+	 * constructs the select use on special limit joins
4751
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4752
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4753
+	 * (as that is typically where the limits would be set).
4754
+	 *
4755
+	 * @param string       $table_alias The table the select is being built for
4756
+	 * @param mixed|string $limit       The limit for this select
4757
+	 * @return string                The final select join element for the query.
4758
+	 * @throws EE_Error
4759
+	 */
4760
+	public function _construct_limit_join_select($table_alias, $limit)
4761
+	{
4762
+		$SQL = '';
4763
+		foreach ($this->_tables as $table_obj) {
4764
+			if ($table_obj instanceof EE_Primary_Table) {
4765
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4766
+					? $table_obj->get_select_join_limit($limit)
4767
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4768
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4769
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4770
+					? $table_obj->get_select_join_limit_join($limit)
4771
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4772
+			}
4773
+		}
4774
+		return $SQL;
4775
+	}
4776
+
4777
+
4778
+	/**
4779
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4780
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4781
+	 *
4782
+	 * @return string SQL
4783
+	 * @throws EE_Error
4784
+	 */
4785
+	public function _construct_internal_join()
4786
+	{
4787
+		$SQL = $this->_get_main_table()->get_table_sql();
4788
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4789
+		return $SQL;
4790
+	}
4791
+
4792
+
4793
+	/**
4794
+	 * Constructs the SQL for joining all the tables on this model.
4795
+	 * Normally $alias should be the primary table's alias, but in cases where
4796
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4797
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4798
+	 * alias, this will construct SQL like:
4799
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4800
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4801
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4802
+	 *
4803
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4804
+	 * @return string
4805
+	 * @throws EE_Error
4806
+	 */
4807
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4808
+	{
4809
+		$SQL               = '';
4810
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4811
+		foreach ($this->_tables as $table_obj) {
4812
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4813
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4814
+					// so we're joining to this table, meaning the table is already in
4815
+					// the FROM statement, BUT the primary table isn't. So we want
4816
+					// to add the inverse join sql
4817
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4818
+				} else {
4819
+					// just add a regular JOIN to this table from the primary table
4820
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4821
+				}
4822
+			}//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4823
+		}
4824
+		return $SQL;
4825
+	}
4826
+
4827
+
4828
+	/**
4829
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4830
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4831
+	 * their data type (eg, '%s', '%d', etc)
4832
+	 *
4833
+	 * @return array
4834
+	 */
4835
+	public function _get_data_types()
4836
+	{
4837
+		$data_types = [];
4838
+		foreach ($this->field_settings() as $field_obj) {
4839
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4840
+			/** @var $field_obj EE_Model_Field_Base */
4841
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4842
+		}
4843
+		return $data_types;
4844
+	}
4845
+
4846
+
4847
+	/**
4848
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4849
+	 *
4850
+	 * @param string $model_name
4851
+	 * @return EEM_Base
4852
+	 * @throws EE_Error
4853
+	 */
4854
+	public function get_related_model_obj($model_name)
4855
+	{
4856
+		$model_classname = "EEM_" . $model_name;
4857
+		if (! class_exists($model_classname)) {
4858
+			throw new EE_Error(
4859
+				sprintf(
4860
+					esc_html__(
4861
+						"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4862
+						'event_espresso'
4863
+					),
4864
+					$model_name,
4865
+					$model_classname
4866
+				)
4867
+			);
4868
+		}
4869
+		return call_user_func($model_classname . "::instance");
4870
+	}
4871
+
4872
+
4873
+	/**
4874
+	 * Returns the array of EE_ModelRelations for this model.
4875
+	 *
4876
+	 * @return EE_Model_Relation_Base[]
4877
+	 */
4878
+	public function relation_settings()
4879
+	{
4880
+		return $this->_model_relations;
4881
+	}
4882
+
4883
+
4884
+	/**
4885
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4886
+	 * because without THOSE models, this model probably doesn't have much purpose.
4887
+	 * (Eg, without an event, datetimes have little purpose.)
4888
+	 *
4889
+	 * @return EE_Belongs_To_Relation[]
4890
+	 */
4891
+	public function belongs_to_relations()
4892
+	{
4893
+		$belongs_to_relations = [];
4894
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4895
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
4896
+				$belongs_to_relations[ $model_name ] = $relation_obj;
4897
+			}
4898
+		}
4899
+		return $belongs_to_relations;
4900
+	}
4901
+
4902
+
4903
+	/**
4904
+	 * Returns the specified EE_Model_Relation, or throws an exception
4905
+	 *
4906
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
4907
+	 * @return EE_Model_Relation_Base
4908
+	 * @throws EE_Error
4909
+	 */
4910
+	public function related_settings_for($relation_name)
4911
+	{
4912
+		$relatedModels = $this->relation_settings();
4913
+		if (! array_key_exists($relation_name, $relatedModels)) {
4914
+			throw new EE_Error(
4915
+				sprintf(
4916
+					esc_html__(
4917
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4918
+						'event_espresso'
4919
+					),
4920
+					$relation_name,
4921
+					$this->_get_class_name(),
4922
+					implode(', ', array_keys($relatedModels))
4923
+				)
4924
+			);
4925
+		}
4926
+		return $relatedModels[ $relation_name ];
4927
+	}
4928
+
4929
+
4930
+	/**
4931
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4932
+	 * fields
4933
+	 *
4934
+	 * @param string  $fieldName
4935
+	 * @param boolean $include_db_only_fields
4936
+	 * @return EE_Model_Field_Base
4937
+	 * @throws EE_Error
4938
+	 */
4939
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
4940
+	{
4941
+		$fieldSettings = $this->field_settings($include_db_only_fields);
4942
+		if (! array_key_exists($fieldName, $fieldSettings)) {
4943
+			throw new EE_Error(
4944
+				sprintf(
4945
+					esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4946
+					$fieldName,
4947
+					get_class($this)
4948
+				)
4949
+			);
4950
+		}
4951
+		return $fieldSettings[ $fieldName ];
4952
+	}
4953
+
4954
+
4955
+	/**
4956
+	 * Checks if this field exists on this model
4957
+	 *
4958
+	 * @param string $fieldName a key in the model's _field_settings array
4959
+	 * @return boolean
4960
+	 */
4961
+	public function has_field($fieldName)
4962
+	{
4963
+		$fieldSettings = $this->field_settings(true);
4964
+		if (isset($fieldSettings[ $fieldName ])) {
4965
+			return true;
4966
+		}
4967
+		return false;
4968
+	}
4969
+
4970
+
4971
+	/**
4972
+	 * Returns whether or not this model has a relation to the specified model
4973
+	 *
4974
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
4975
+	 * @return boolean
4976
+	 */
4977
+	public function has_relation($relation_name)
4978
+	{
4979
+		$relations = $this->relation_settings();
4980
+		if (isset($relations[ $relation_name ])) {
4981
+			return true;
4982
+		}
4983
+		return false;
4984
+	}
4985
+
4986
+
4987
+	/**
4988
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4989
+	 * Eg, on EE_Answer that would be ANS_ID field object
4990
+	 *
4991
+	 * @param $field_obj
4992
+	 * @return boolean
4993
+	 */
4994
+	public function is_primary_key_field($field_obj)
4995
+	{
4996
+		return $field_obj instanceof EE_Primary_Key_Field_Base;
4997
+	}
4998
+
4999
+
5000
+	/**
5001
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5002
+	 * Eg, on EE_Answer that would be ANS_ID field object
5003
+	 *
5004
+	 * @return EE_Model_Field_Base
5005
+	 * @throws EE_Error
5006
+	 */
5007
+	public function get_primary_key_field()
5008
+	{
5009
+		if ($this->_primary_key_field === null) {
5010
+			foreach ($this->field_settings(true) as $field_obj) {
5011
+				if ($this->is_primary_key_field($field_obj)) {
5012
+					$this->_primary_key_field = $field_obj;
5013
+					break;
5014
+				}
5015
+			}
5016
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5017
+				throw new EE_Error(
5018
+					sprintf(
5019
+						esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5020
+						get_class($this)
5021
+					)
5022
+				);
5023
+			}
5024
+		}
5025
+		return $this->_primary_key_field;
5026
+	}
5027
+
5028
+
5029
+	/**
5030
+	 * Returns whether or not not there is a primary key on this model.
5031
+	 * Internally does some caching.
5032
+	 *
5033
+	 * @return boolean
5034
+	 */
5035
+	public function has_primary_key_field()
5036
+	{
5037
+		if ($this->_has_primary_key_field === null) {
5038
+			try {
5039
+				$this->get_primary_key_field();
5040
+				$this->_has_primary_key_field = true;
5041
+			} catch (EE_Error $e) {
5042
+				$this->_has_primary_key_field = false;
5043
+			}
5044
+		}
5045
+		return $this->_has_primary_key_field;
5046
+	}
5047
+
5048
+
5049
+	/**
5050
+	 * Finds the first field of type $field_class_name.
5051
+	 *
5052
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5053
+	 *                                 EE_Foreign_Key_Field, etc
5054
+	 * @return EE_Model_Field_Base or null if none is found
5055
+	 */
5056
+	public function get_a_field_of_type($field_class_name)
5057
+	{
5058
+		foreach ($this->field_settings() as $field) {
5059
+			if ($field instanceof $field_class_name) {
5060
+				return $field;
5061
+			}
5062
+		}
5063
+		return null;
5064
+	}
5065
+
5066
+
5067
+	/**
5068
+	 * Gets a foreign key field pointing to model.
5069
+	 *
5070
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5071
+	 * @return EE_Foreign_Key_Field_Base
5072
+	 * @throws EE_Error
5073
+	 */
5074
+	public function get_foreign_key_to($model_name)
5075
+	{
5076
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5077
+			foreach ($this->field_settings() as $field) {
5078
+				if (
5079
+					$field instanceof EE_Foreign_Key_Field_Base
5080
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5081
+				) {
5082
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5083
+					break;
5084
+				}
5085
+			}
5086
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5087
+				throw new EE_Error(
5088
+					sprintf(
5089
+						esc_html__(
5090
+							"There is no foreign key field pointing to model %s on model %s",
5091
+							'event_espresso'
5092
+						),
5093
+						$model_name,
5094
+						get_class($this)
5095
+					)
5096
+				);
5097
+			}
5098
+		}
5099
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5100
+	}
5101
+
5102
+
5103
+	/**
5104
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5105
+	 *
5106
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5107
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5108
+	 *                            Either one works
5109
+	 * @return string
5110
+	 */
5111
+	public function get_table_for_alias($table_alias)
5112
+	{
5113
+		$table_alias_sans_model_relation_chain_prefix =
5114
+			EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5115
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5116
+	}
5117
+
5118
+
5119
+	/**
5120
+	 * Returns a flat array of all field son this model, instead of organizing them
5121
+	 * by table_alias as they are in the constructor.
5122
+	 *
5123
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5124
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5125
+	 */
5126
+	public function field_settings($include_db_only_fields = false)
5127
+	{
5128
+		if ($include_db_only_fields) {
5129
+			if ($this->_cached_fields === null) {
5130
+				$this->_cached_fields = [];
5131
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5132
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5133
+						$this->_cached_fields[ $field_name ] = $field_obj;
5134
+					}
5135
+				}
5136
+			}
5137
+			return $this->_cached_fields;
5138
+		}
5139
+		if ($this->_cached_fields_non_db_only === null) {
5140
+			$this->_cached_fields_non_db_only = [];
5141
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5142
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5143
+					/** @var $field_obj EE_Model_Field_Base */
5144
+					if (! $field_obj->is_db_only_field()) {
5145
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5146
+					}
5147
+				}
5148
+			}
5149
+		}
5150
+		return $this->_cached_fields_non_db_only;
5151
+	}
5152
+
5153
+
5154
+	/**
5155
+	 *        cycle though array of attendees and create objects out of each item
5156
+	 *
5157
+	 * @access        private
5158
+	 * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5159
+	 * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5160
+	 *                           numerically indexed)
5161
+	 * @throws EE_Error
5162
+	 * @throws ReflectionException
5163
+	 */
5164
+	protected function _create_objects($rows = [])
5165
+	{
5166
+		$array_of_objects = [];
5167
+		if (empty($rows)) {
5168
+			return [];
5169
+		}
5170
+		$count_if_model_has_no_primary_key = 0;
5171
+		$has_primary_key                   = $this->has_primary_key_field();
5172
+		$primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5173
+		foreach ((array)$rows as $row) {
5174
+			if (empty($row)) {
5175
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5176
+				return [];
5177
+			}
5178
+			// check if we've already set this object in the results array,
5179
+			// in which case there's no need to process it further (again)
5180
+			if ($has_primary_key) {
5181
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5182
+					$row,
5183
+					$primary_key_field->get_qualified_column(),
5184
+					$primary_key_field->get_table_column()
5185
+				);
5186
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5187
+					continue;
5188
+				}
5189
+			}
5190
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5191
+			if (! $classInstance) {
5192
+				throw new EE_Error(
5193
+					sprintf(
5194
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5195
+						$this->get_this_model_name(),
5196
+						http_build_query($row)
5197
+					)
5198
+				);
5199
+			}
5200
+			// set the timezone on the instantiated objects
5201
+			$classInstance->set_timezone($this->_timezone);
5202
+			// make sure if there is any timezone setting present that we set the timezone for the object
5203
+			$key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5204
+			$array_of_objects[ $key ] = $classInstance;
5205
+			// also, for all the relations of type BelongsTo, see if we can cache
5206
+			// those related models
5207
+			// (we could do this for other relations too, but if there are conditions
5208
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5209
+			// so it requires a little more thought than just caching them immediately...)
5210
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5211
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5212
+					// check if this model's INFO is present. If so, cache it on the model
5213
+					$other_model           = $relation_obj->get_other_model();
5214
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5215
+					// if we managed to make a model object from the results, cache it on the main model object
5216
+					if ($other_model_obj_maybe) {
5217
+						// set timezone on these other model objects if they are present
5218
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5219
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5220
+					}
5221
+				}
5222
+			}
5223
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5224
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5225
+			// the field in the CustomSelects object
5226
+			if ($this->_custom_selections instanceof CustomSelects) {
5227
+				$classInstance->setCustomSelectsValues(
5228
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5229
+				);
5230
+			}
5231
+		}
5232
+		return $array_of_objects;
5233
+	}
5234
+
5235
+
5236
+	/**
5237
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5238
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5239
+	 *
5240
+	 * @param array $db_results_row
5241
+	 * @return array
5242
+	 */
5243
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5244
+	{
5245
+		$results = [];
5246
+		if ($this->_custom_selections instanceof CustomSelects) {
5247
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5248
+				if (isset($db_results_row[ $alias ])) {
5249
+					$results[ $alias ] = $this->convertValueToDataType(
5250
+						$db_results_row[ $alias ],
5251
+						$this->_custom_selections->getDataTypeForAlias($alias)
5252
+					);
5253
+				}
5254
+			}
5255
+		}
5256
+		return $results;
5257
+	}
5258
+
5259
+
5260
+	/**
5261
+	 * This will set the value for the given alias
5262
+	 *
5263
+	 * @param string $value
5264
+	 * @param string $datatype (one of %d, %s, %f)
5265
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5266
+	 */
5267
+	protected function convertValueToDataType($value, $datatype)
5268
+	{
5269
+		switch ($datatype) {
5270
+			case '%f':
5271
+				return (float)$value;
5272
+			case '%d':
5273
+				return (int)$value;
5274
+			default:
5275
+				return (string)$value;
5276
+		}
5277
+	}
5278
+
5279
+
5280
+	/**
5281
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5282
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5283
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5284
+	 * object (as set in the model_field!).
5285
+	 *
5286
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5287
+	 * @throws EE_Error
5288
+	 * @throws ReflectionException
5289
+	 */
5290
+	public function create_default_object()
5291
+	{
5292
+		$this_model_fields_and_values = [];
5293
+		// setup the row using default values;
5294
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5295
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5296
+		}
5297
+		$className = $this->_get_class_name();
5298
+		return EE_Registry::instance()->load_class(
5299
+			$className,
5300
+			[$this_model_fields_and_values],
5301
+			false,
5302
+			false
5303
+		);
5304
+	}
5305
+
5306
+
5307
+	/**
5308
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5309
+	 *                             or an stdClass where each property is the name of a column,
5310
+	 * @return EE_Base_Class
5311
+	 * @throws EE_Error
5312
+	 * @throws ReflectionException
5313
+	 */
5314
+	public function instantiate_class_from_array_or_object($cols_n_values)
5315
+	{
5316
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5317
+			$cols_n_values = get_object_vars($cols_n_values);
5318
+		}
5319
+		$primary_key = null;
5320
+		// make sure the array only has keys that are fields/columns on this model
5321
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5322
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5323
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5324
+		}
5325
+		$className = $this->_get_class_name();
5326
+		// check we actually found results that we can use to build our model object
5327
+		// if not, return null
5328
+		if ($this->has_primary_key_field()) {
5329
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5330
+				return null;
5331
+			}
5332
+		} elseif ($this->unique_indexes()) {
5333
+			$first_column = reset($this_model_fields_n_values);
5334
+			if (empty($first_column)) {
5335
+				return null;
5336
+			}
5337
+		}
5338
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5339
+		if ($primary_key) {
5340
+			$classInstance = $this->get_from_entity_map($primary_key);
5341
+			if (! $classInstance) {
5342
+				$classInstance = EE_Registry::instance()->load_class(
5343
+					$className,
5344
+					[$this_model_fields_n_values, $this->_timezone],
5345
+					true,
5346
+					false
5347
+				);
5348
+				// add this new object to the entity map
5349
+				$classInstance = $this->add_to_entity_map($classInstance);
5350
+			}
5351
+		} else {
5352
+			$classInstance = EE_Registry::instance()->load_class(
5353
+				$className,
5354
+				[$this_model_fields_n_values, $this->_timezone],
5355
+				true,
5356
+				false
5357
+			);
5358
+		}
5359
+		return $classInstance;
5360
+	}
5361
+
5362
+
5363
+	/**
5364
+	 * Gets the model object from the  entity map if it exists
5365
+	 *
5366
+	 * @param int|string $id the ID of the model object
5367
+	 * @return EE_Base_Class
5368
+	 */
5369
+	public function get_from_entity_map($id)
5370
+	{
5371
+		return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5372
+			? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5373
+	}
5374
+
5375
+
5376
+	/**
5377
+	 * add_to_entity_map
5378
+	 * Adds the object to the model's entity mappings
5379
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5380
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5381
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5382
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5383
+	 *        then this method should be called immediately after the update query
5384
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5385
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5386
+	 *
5387
+	 * @param EE_Base_Class $object
5388
+	 * @return EE_Base_Class
5389
+	 * @throws EE_Error
5390
+	 * @throws ReflectionException
5391
+	 */
5392
+	public function add_to_entity_map(EE_Base_Class $object)
5393
+	{
5394
+		$className = $this->_get_class_name();
5395
+		if (! $object instanceof $className) {
5396
+			throw new EE_Error(
5397
+				sprintf(
5398
+					esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5399
+					is_object($object) ? get_class($object) : $object,
5400
+					$className
5401
+				)
5402
+			);
5403
+		}
5404
+		if (! $object->ID()) {
5405
+			throw new EE_Error(
5406
+				sprintf(
5407
+					esc_html__(
5408
+						"You tried storing a model object with NO ID in the %s entity mapper.",
5409
+						"event_espresso"
5410
+					),
5411
+					get_class($this)
5412
+				)
5413
+			);
5414
+		}
5415
+		// double check it's not already there
5416
+		$classInstance = $this->get_from_entity_map($object->ID());
5417
+		if ($classInstance) {
5418
+			return $classInstance;
5419
+		}
5420
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5421
+		return $object;
5422
+	}
5423
+
5424
+
5425
+	/**
5426
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5427
+	 * if no identifier is provided, then the entire entity map is emptied
5428
+	 *
5429
+	 * @param int|string $id the ID of the model object
5430
+	 * @return boolean
5431
+	 */
5432
+	public function clear_entity_map($id = null)
5433
+	{
5434
+		if (empty($id)) {
5435
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5436
+			return true;
5437
+		}
5438
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5439
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5440
+			return true;
5441
+		}
5442
+		return false;
5443
+	}
5444
+
5445
+
5446
+	/**
5447
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5448
+	 * Given an array where keys are column (or column alias) names and values,
5449
+	 * returns an array of their corresponding field names and database values
5450
+	 *
5451
+	 * @param array $cols_n_values
5452
+	 * @return array
5453
+	 */
5454
+	public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5455
+	{
5456
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5457
+	}
5458
+
5459
+
5460
+	/**
5461
+	 * _deduce_fields_n_values_from_cols_n_values
5462
+	 * Given an array where keys are column (or column alias) names and values,
5463
+	 * returns an array of their corresponding field names and database values
5464
+	 *
5465
+	 * @param array $cols_n_values
5466
+	 * @return array
5467
+	 */
5468
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5469
+	{
5470
+		$this_model_fields_n_values = [];
5471
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5472
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5473
+				$cols_n_values,
5474
+				$table_obj->get_fully_qualified_pk_column(),
5475
+				$table_obj->get_pk_column()
5476
+			);
5477
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5478
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5479
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5480
+					if (! $field_obj->is_db_only_field()) {
5481
+						// prepare field as if its coming from db
5482
+						$prepared_value                            =
5483
+							$field_obj->prepare_for_set($field_obj->get_default_value());
5484
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5485
+					}
5486
+				}
5487
+			} else {
5488
+				// the table's rows existed. Use their values
5489
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5490
+					if (! $field_obj->is_db_only_field()) {
5491
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5492
+							$cols_n_values,
5493
+							$field_obj->get_qualified_column(),
5494
+							$field_obj->get_table_column()
5495
+						);
5496
+					}
5497
+				}
5498
+			}
5499
+		}
5500
+		return $this_model_fields_n_values;
5501
+	}
5502
+
5503
+
5504
+	/**
5505
+	 * @param array  $cols_n_values
5506
+	 * @param string $qualified_column
5507
+	 * @param string $regular_column
5508
+	 * @return null
5509
+	 */
5510
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5511
+	{
5512
+		$value = null;
5513
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5514
+		// does the field on the model relate to this column retrieved from the db?
5515
+		// or is it a db-only field? (not relating to the model)
5516
+		if (isset($cols_n_values[ $qualified_column ])) {
5517
+			$value = $cols_n_values[ $qualified_column ];
5518
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5519
+			$value = $cols_n_values[ $regular_column ];
5520
+		} elseif (! empty($this->foreign_key_aliases)) {
5521
+			// no PK?  ok check if there is a foreign key alias set for this table
5522
+			// then check if that alias exists in the incoming data
5523
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5524
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5525
+				if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5526
+					$value = $cols_n_values[ $FK_alias ];
5527
+					break;
5528
+				}
5529
+			}
5530
+		}
5531
+		return $value;
5532
+	}
5533
+
5534
+
5535
+	/**
5536
+	 * refresh_entity_map_from_db
5537
+	 * Makes sure the model object in the entity map at $id assumes the values
5538
+	 * of the database (opposite of EE_base_Class::save())
5539
+	 *
5540
+	 * @param int|string $id
5541
+	 * @return EE_Base_Class
5542
+	 * @throws EE_Error
5543
+	 * @throws ReflectionException
5544
+	 */
5545
+	public function refresh_entity_map_from_db($id)
5546
+	{
5547
+		$obj_in_map = $this->get_from_entity_map($id);
5548
+		if ($obj_in_map) {
5549
+			$wpdb_results = $this->_get_all_wpdb_results(
5550
+				[[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5551
+			);
5552
+			if ($wpdb_results && is_array($wpdb_results)) {
5553
+				$one_row = reset($wpdb_results);
5554
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5555
+					$obj_in_map->set_from_db($field_name, $db_value);
5556
+				}
5557
+				// clear the cache of related model objects
5558
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5559
+					$obj_in_map->clear_cache($relation_name, null, true);
5560
+				}
5561
+			}
5562
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5563
+			return $obj_in_map;
5564
+		}
5565
+		return $this->get_one_by_ID($id);
5566
+	}
5567
+
5568
+
5569
+	/**
5570
+	 * refresh_entity_map_with
5571
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5572
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5573
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5574
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5575
+	 *
5576
+	 * @param int|string    $id
5577
+	 * @param EE_Base_Class $replacing_model_obj
5578
+	 * @return EE_Base_Class
5579
+	 * @throws EE_Error
5580
+	 * @throws ReflectionException
5581
+	 */
5582
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5583
+	{
5584
+		$obj_in_map = $this->get_from_entity_map($id);
5585
+		if ($obj_in_map) {
5586
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5587
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5588
+					$obj_in_map->set($field_name, $value);
5589
+				}
5590
+				// make the model object in the entity map's cache match the $replacing_model_obj
5591
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5592
+					$obj_in_map->clear_cache($relation_name, null, true);
5593
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5594
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5595
+					}
5596
+				}
5597
+			}
5598
+			return $obj_in_map;
5599
+		}
5600
+		$this->add_to_entity_map($replacing_model_obj);
5601
+		return $replacing_model_obj;
5602
+	}
5603
+
5604
+
5605
+	/**
5606
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5607
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5608
+	 * require_once($this->_getClassName().".class.php");
5609
+	 *
5610
+	 * @return string
5611
+	 */
5612
+	private function _get_class_name()
5613
+	{
5614
+		return "EE_" . $this->get_this_model_name();
5615
+	}
5616
+
5617
+
5618
+	/**
5619
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5620
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5621
+	 * it would be 'Events'.
5622
+	 *
5623
+	 * @param int $quantity
5624
+	 * @return string
5625
+	 */
5626
+	public function item_name($quantity = 1)
5627
+	{
5628
+		return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5629
+	}
5630
+
5631
+
5632
+	/**
5633
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5634
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5635
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5636
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5637
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5638
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5639
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5640
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5641
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5642
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5643
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5644
+	 *        return $previousReturnValue.$returnString;
5645
+	 * }
5646
+	 * require('EEM_Answer.model.php');
5647
+	 * $answer=EEM_Answer::instance();
5648
+	 * echo $answer->my_callback('monkeys',100);
5649
+	 * //will output "you called my_callback! and passed args:monkeys,100"
5650
+	 *
5651
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5652
+	 * @param array  $args       array of original arguments passed to the function
5653
+	 * @return mixed whatever the plugin which calls add_filter decides
5654
+	 * @throws EE_Error
5655
+	 */
5656
+	public function __call($methodName, $args)
5657
+	{
5658
+		$className = get_class($this);
5659
+		$tagName   = "FHEE__{$className}__{$methodName}";
5660
+		if (! has_filter($tagName)) {
5661
+			throw new EE_Error(
5662
+				sprintf(
5663
+					esc_html__(
5664
+						'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 );',
5665
+						'event_espresso'
5666
+					),
5667
+					$methodName,
5668
+					$className,
5669
+					$tagName,
5670
+					'<br />'
5671
+				)
5672
+			);
5673
+		}
5674
+		return apply_filters($tagName, null, $this, $args);
5675
+	}
5676
+
5677
+
5678
+	/**
5679
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5680
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5681
+	 *
5682
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5683
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5684
+	 *                                                       the object's class name
5685
+	 *                                                       or object's ID
5686
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5687
+	 *                                                       exists in the database. If it does not, we add it
5688
+	 * @return EE_Base_Class
5689
+	 * @throws EE_Error
5690
+	 * @throws ReflectionException
5691
+	 */
5692
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5693
+	{
5694
+		$className = $this->_get_class_name();
5695
+		if ($base_class_obj_or_id instanceof $className) {
5696
+			$model_object = $base_class_obj_or_id;
5697
+		} else {
5698
+			$primary_key_field = $this->get_primary_key_field();
5699
+			if (
5700
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5701
+				&& (
5702
+					is_int($base_class_obj_or_id)
5703
+					|| is_string($base_class_obj_or_id)
5704
+				)
5705
+			) {
5706
+				// assume it's an ID.
5707
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5708
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5709
+			} elseif (
5710
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5711
+				&& is_string($base_class_obj_or_id)
5712
+			) {
5713
+				// assume its a string representation of the object
5714
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5715
+			} else {
5716
+				throw new EE_Error(
5717
+					sprintf(
5718
+						esc_html__(
5719
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5720
+							'event_espresso'
5721
+						),
5722
+						$base_class_obj_or_id,
5723
+						$this->_get_class_name(),
5724
+						print_r($base_class_obj_or_id, true)
5725
+					)
5726
+				);
5727
+			}
5728
+		}
5729
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5730
+			$model_object->save();
5731
+		}
5732
+		return $model_object;
5733
+	}
5734
+
5735
+
5736
+	/**
5737
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5738
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5739
+	 * returns it ID.
5740
+	 *
5741
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5742
+	 * @return int|string depending on the type of this model object's ID
5743
+	 * @throws EE_Error
5744
+	 * @throws ReflectionException
5745
+	 */
5746
+	public function ensure_is_ID($base_class_obj_or_id)
5747
+	{
5748
+		$className = $this->_get_class_name();
5749
+		if ($base_class_obj_or_id instanceof $className) {
5750
+			$id = $base_class_obj_or_id->ID();
5751
+		} elseif (is_int($base_class_obj_or_id)) {
5752
+			// assume it's an ID
5753
+			$id = $base_class_obj_or_id;
5754
+		} elseif (is_string($base_class_obj_or_id)) {
5755
+			// assume its a string representation of the object
5756
+			$id = $base_class_obj_or_id;
5757
+		} else {
5758
+			throw new EE_Error(
5759
+				sprintf(
5760
+					esc_html__(
5761
+						"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5762
+						'event_espresso'
5763
+					),
5764
+					$base_class_obj_or_id,
5765
+					$this->_get_class_name(),
5766
+					print_r($base_class_obj_or_id, true)
5767
+				)
5768
+			);
5769
+		}
5770
+		return $id;
5771
+	}
5772
+
5773
+
5774
+	/**
5775
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5776
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5777
+	 * been sanitized and converted into the appropriate domain.
5778
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5779
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5780
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5781
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5782
+	 * $EVT = EEM_Event::instance(); $old_setting =
5783
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5784
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5785
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5786
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5787
+	 *
5788
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5789
+	 * @return void
5790
+	 */
5791
+	public function assume_values_already_prepared_by_model_object(
5792
+		$values_already_prepared = self::not_prepared_by_model_object
5793
+	) {
5794
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5795
+	}
5796
+
5797
+
5798
+	/**
5799
+	 * Read comments for assume_values_already_prepared_by_model_object()
5800
+	 *
5801
+	 * @return int
5802
+	 */
5803
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5804
+	{
5805
+		return $this->_values_already_prepared_by_model_object;
5806
+	}
5807
+
5808
+
5809
+	/**
5810
+	 * Gets all the indexes on this model
5811
+	 *
5812
+	 * @return EE_Index[]
5813
+	 */
5814
+	public function indexes()
5815
+	{
5816
+		return $this->_indexes;
5817
+	}
5818
+
5819
+
5820
+	/**
5821
+	 * Gets all the Unique Indexes on this model
5822
+	 *
5823
+	 * @return EE_Unique_Index[]
5824
+	 */
5825
+	public function unique_indexes()
5826
+	{
5827
+		$unique_indexes = [];
5828
+		foreach ($this->_indexes as $name => $index) {
5829
+			if ($index instanceof EE_Unique_Index) {
5830
+				$unique_indexes [ $name ] = $index;
5831
+			}
5832
+		}
5833
+		return $unique_indexes;
5834
+	}
5835
+
5836
+
5837
+	/**
5838
+	 * Gets all the fields which, when combined, make the primary key.
5839
+	 * This is usually just an array with 1 element (the primary key), but in cases
5840
+	 * where there is no primary key, it's a combination of fields as defined
5841
+	 * on a primary index
5842
+	 *
5843
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5844
+	 * @throws EE_Error
5845
+	 */
5846
+	public function get_combined_primary_key_fields()
5847
+	{
5848
+		foreach ($this->indexes() as $index) {
5849
+			if ($index instanceof EE_Primary_Key_Index) {
5850
+				return $index->fields();
5851
+			}
5852
+		}
5853
+		return [$this->primary_key_name() => $this->get_primary_key_field()];
5854
+	}
5855
+
5856
+
5857
+	/**
5858
+	 * Used to build a primary key string (when the model has no primary key),
5859
+	 * which can be used a unique string to identify this model object.
5860
+	 *
5861
+	 * @param array $fields_n_values keys are field names, values are their values.
5862
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5863
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5864
+	 *                               before passing it to this function (that will convert it from columns-n-values
5865
+	 *                               to field-names-n-values).
5866
+	 * @return string
5867
+	 * @throws EE_Error
5868
+	 */
5869
+	public function get_index_primary_key_string($fields_n_values)
5870
+	{
5871
+		$cols_n_values_for_primary_key_index = array_intersect_key(
5872
+			$fields_n_values,
5873
+			$this->get_combined_primary_key_fields()
5874
+		);
5875
+		return http_build_query($cols_n_values_for_primary_key_index);
5876
+	}
5877
+
5878
+
5879
+	/**
5880
+	 * Gets the field values from the primary key string
5881
+	 *
5882
+	 * @param string $index_primary_key_string
5883
+	 * @return null|array
5884
+	 * @throws EE_Error
5885
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5886
+	 */
5887
+	public function parse_index_primary_key_string($index_primary_key_string)
5888
+	{
5889
+		$key_fields = $this->get_combined_primary_key_fields();
5890
+		// check all of them are in the $id
5891
+		$key_values_in_combined_pk = [];
5892
+		parse_str($index_primary_key_string, $key_values_in_combined_pk);
5893
+		foreach ($key_fields as $key_field_name => $field_obj) {
5894
+			if (! isset($key_values_in_combined_pk[ $key_field_name ])) {
5895
+				return null;
5896
+			}
5897
+		}
5898
+		return $key_values_in_combined_pk;
5899
+	}
5900
+
5901
+
5902
+	/**
5903
+	 * verifies that an array of key-value pairs for model fields has a key
5904
+	 * for each field comprising the primary key index
5905
+	 *
5906
+	 * @param array $key_values
5907
+	 * @return boolean
5908
+	 * @throws EE_Error
5909
+	 */
5910
+	public function has_all_combined_primary_key_fields($key_values)
5911
+	{
5912
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5913
+		foreach ($keys_it_should_have as $key) {
5914
+			if (! isset($key_values[ $key ])) {
5915
+				return false;
5916
+			}
5917
+		}
5918
+		return true;
5919
+	}
5920
+
5921
+
5922
+	/**
5923
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5924
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
5925
+	 *
5926
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5927
+	 * @param array               $query_params                     see github link below for more info
5928
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5929
+	 * @throws EE_Error
5930
+	 * @throws ReflectionException
5931
+	 * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5932
+	 *                                                              indexed)
5933
+	 */
5934
+	public function get_all_copies($model_object_or_attributes_array, $query_params = [])
5935
+	{
5936
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5937
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
5938
+		} elseif (is_array($model_object_or_attributes_array)) {
5939
+			$attributes_array = $model_object_or_attributes_array;
5940
+		} else {
5941
+			throw new EE_Error(
5942
+				sprintf(
5943
+					esc_html__(
5944
+						"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5945
+						"event_espresso"
5946
+					),
5947
+					$model_object_or_attributes_array
5948
+				)
5949
+			);
5950
+		}
5951
+		// even copies obviously won't have the same ID, so remove the primary key
5952
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
5953
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5954
+			unset($attributes_array[ $this->primary_key_name() ]);
5955
+		}
5956
+		if (isset($query_params[0])) {
5957
+			$query_params[0] = array_merge($attributes_array, $query_params);
5958
+		} else {
5959
+			$query_params[0] = $attributes_array;
5960
+		}
5961
+		return $this->get_all($query_params);
5962
+	}
5963
+
5964
+
5965
+	/**
5966
+	 * Gets the first copy we find. See get_all_copies for more details
5967
+	 *
5968
+	 * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
5969
+	 * @param array $query_params
5970
+	 * @return EE_Base_Class
5971
+	 * @throws EE_Error
5972
+	 * @throws ReflectionException
5973
+	 */
5974
+	public function get_one_copy($model_object_or_attributes_array, $query_params = [])
5975
+	{
5976
+		if (! is_array($query_params)) {
5977
+			EE_Error::doing_it_wrong(
5978
+				'EEM_Base::get_one_copy',
5979
+				sprintf(
5980
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5981
+					gettype($query_params)
5982
+				),
5983
+				'4.6.0'
5984
+			);
5985
+			$query_params = [];
5986
+		}
5987
+		$query_params['limit'] = 1;
5988
+		$copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5989
+		if (is_array($copies)) {
5990
+			return array_shift($copies);
5991
+		}
5992
+		return null;
5993
+	}
5994
+
5995
+
5996
+	/**
5997
+	 * Updates the item with the specified id. Ignores default query parameters because
5998
+	 * we have specified the ID, and its assumed we KNOW what we're doing
5999
+	 *
6000
+	 * @param array      $fields_n_values keys are field names, values are their new values
6001
+	 * @param int|string $id              the value of the primary key to update
6002
+	 * @return int number of rows updated
6003
+	 * @throws EE_Error
6004
+	 * @throws ReflectionException
6005
+	 */
6006
+	public function update_by_ID($fields_n_values, $id)
6007
+	{
6008
+		$query_params = [
6009
+			0                          => [$this->get_primary_key_field()->get_name() => $id],
6010
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6011
+		];
6012
+		return $this->update($fields_n_values, $query_params);
6013
+	}
6014
+
6015
+
6016
+	/**
6017
+	 * Changes an operator which was supplied to the models into one usable in SQL
6018
+	 *
6019
+	 * @param string $operator_supplied
6020
+	 * @return string an operator which can be used in SQL
6021
+	 * @throws EE_Error
6022
+	 */
6023
+	private function _prepare_operator_for_sql($operator_supplied)
6024
+	{
6025
+		$sql_operator =
6026
+			isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6027
+				: null;
6028
+		if ($sql_operator) {
6029
+			return $sql_operator;
6030
+		}
6031
+		throw new EE_Error(
6032
+			sprintf(
6033
+				esc_html__(
6034
+					"The operator '%s' is not in the list of valid operators: %s",
6035
+					"event_espresso"
6036
+				),
6037
+				$operator_supplied,
6038
+				implode(",", array_keys($this->_valid_operators))
6039
+			)
6040
+		);
6041
+	}
6042
+
6043
+
6044
+	/**
6045
+	 * Gets the valid operators
6046
+	 *
6047
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6048
+	 */
6049
+	public function valid_operators()
6050
+	{
6051
+		return $this->_valid_operators;
6052
+	}
6053
+
6054
+
6055
+	/**
6056
+	 * Gets the between-style operators (take 2 arguments).
6057
+	 *
6058
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6059
+	 */
6060
+	public function valid_between_style_operators()
6061
+	{
6062
+		return array_intersect(
6063
+			$this->valid_operators(),
6064
+			$this->_between_style_operators
6065
+		);
6066
+	}
6067
+
6068
+
6069
+	/**
6070
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6071
+	 *
6072
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6073
+	 */
6074
+	public function valid_like_style_operators()
6075
+	{
6076
+		return array_intersect(
6077
+			$this->valid_operators(),
6078
+			$this->_like_style_operators
6079
+		);
6080
+	}
6081
+
6082
+
6083
+	/**
6084
+	 * Gets the "in"-style operators
6085
+	 *
6086
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6087
+	 */
6088
+	public function valid_in_style_operators()
6089
+	{
6090
+		return array_intersect(
6091
+			$this->valid_operators(),
6092
+			$this->_in_style_operators
6093
+		);
6094
+	}
6095
+
6096
+
6097
+	/**
6098
+	 * Gets the "null"-style operators (accept no arguments)
6099
+	 *
6100
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6101
+	 */
6102
+	public function valid_null_style_operators()
6103
+	{
6104
+		return array_intersect(
6105
+			$this->valid_operators(),
6106
+			$this->_null_style_operators
6107
+		);
6108
+	}
6109
+
6110
+
6111
+	/**
6112
+	 * Gets an array where keys are the primary keys and values are their 'names'
6113
+	 * (as determined by the model object's name() function, which is often overridden)
6114
+	 *
6115
+	 * @param array $query_params like get_all's
6116
+	 * @return string[]
6117
+	 * @throws EE_Error
6118
+	 * @throws ReflectionException
6119
+	 */
6120
+	public function get_all_names($query_params = [])
6121
+	{
6122
+		$objs  = $this->get_all($query_params);
6123
+		$names = [];
6124
+		foreach ($objs as $obj) {
6125
+			$names[ $obj->ID() ] = $obj->name();
6126
+		}
6127
+		return $names;
6128
+	}
6129
+
6130
+
6131
+	/**
6132
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6133
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6134
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6135
+	 * array_keys() on $model_objects.
6136
+	 *
6137
+	 * @param EE_Base_Class[] $model_objects
6138
+	 * @param boolean         $filter_out_empty_ids  if a model object has an ID of '' or 0, don't bother including it
6139
+	 *                                               in the returned array
6140
+	 * @return array
6141
+	 * @throws EE_Error
6142
+	 * @throws ReflectionException
6143
+	 */
6144
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6145
+	{
6146
+		if (! $this->has_primary_key_field()) {
6147
+			if (WP_DEBUG) {
6148
+				EE_Error::add_error(
6149
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6150
+					__FILE__,
6151
+					__FUNCTION__,
6152
+					__LINE__
6153
+				);
6154
+			}
6155
+		}
6156
+		$IDs = [];
6157
+		foreach ($model_objects as $model_object) {
6158
+			$id = $model_object->ID();
6159
+			if (! $id) {
6160
+				if ($filter_out_empty_ids) {
6161
+					continue;
6162
+				}
6163
+				if (WP_DEBUG) {
6164
+					EE_Error::add_error(
6165
+						esc_html__(
6166
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6167
+							'event_espresso'
6168
+						),
6169
+						__FILE__,
6170
+						__FUNCTION__,
6171
+						__LINE__
6172
+					);
6173
+				}
6174
+			}
6175
+			$IDs[] = $id;
6176
+		}
6177
+		return $IDs;
6178
+	}
6179
+
6180
+
6181
+	/**
6182
+	 * Returns the string used in capabilities relating to this model. If there
6183
+	 * are no capabilities that relate to this model returns false
6184
+	 *
6185
+	 * @return string|false
6186
+	 */
6187
+	public function cap_slug()
6188
+	{
6189
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6190
+	}
6191
+
6192
+
6193
+	/**
6194
+	 * Returns the capability-restrictions array (@param string $context
6195
+	 *
6196
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6197
+	 * @throws EE_Error
6198
+	 * @see EEM_Base::_cap_restrictions).
6199
+	 *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6200
+	 *      only returns the cap restrictions array in that context (ie, the array
6201
+	 *      at that key)
6202
+	 *
6203
+	 */
6204
+	public function cap_restrictions($context = EEM_Base::caps_read)
6205
+	{
6206
+		EEM_Base::verify_is_valid_cap_context($context);
6207
+		// check if we ought to run the restriction generator first
6208
+		if (
6209
+			isset($this->_cap_restriction_generators[ $context ])
6210
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6211
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6212
+		) {
6213
+			$this->_cap_restrictions[ $context ] = array_merge(
6214
+				$this->_cap_restrictions[ $context ],
6215
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6216
+			);
6217
+		}
6218
+		// and make sure we've finalized the construction of each restriction
6219
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6220
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6221
+				$where_conditions_obj->_finalize_construct($this);
6222
+			}
6223
+		}
6224
+		return $this->_cap_restrictions[ $context ];
6225
+	}
6226
+
6227
+
6228
+	/**
6229
+	 * Indicating whether or not this model thinks its a wp core model
6230
+	 *
6231
+	 * @return boolean
6232
+	 */
6233
+	public function is_wp_core_model()
6234
+	{
6235
+		return $this->_wp_core_model;
6236
+	}
6237
+
6238
+
6239
+	/**
6240
+	 * Gets all the caps that are missing which impose a restriction on
6241
+	 * queries made in this context
6242
+	 *
6243
+	 * @param string $context one of EEM_Base::caps_ constants
6244
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6245
+	 * @throws EE_Error
6246
+	 */
6247
+	public function caps_missing($context = EEM_Base::caps_read)
6248
+	{
6249
+		$missing_caps     = [];
6250
+		$cap_restrictions = $this->cap_restrictions($context);
6251
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6252
+			if (
6253
+			! EE_Capabilities::instance()
6254
+							 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6255
+			) {
6256
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6257
+			}
6258
+		}
6259
+		return $missing_caps;
6260
+	}
6261
+
6262
+
6263
+	/**
6264
+	 * Gets the mapping from capability contexts to action strings used in capability names
6265
+	 *
6266
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6267
+	 * one of 'read', 'edit', or 'delete'
6268
+	 */
6269
+	public function cap_contexts_to_cap_action_map()
6270
+	{
6271
+		return apply_filters(
6272
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6273
+			$this->_cap_contexts_to_cap_action_map,
6274
+			$this
6275
+		);
6276
+	}
6277
+
6278
+
6279
+	/**
6280
+	 * Gets the action string for the specified capability context
6281
+	 *
6282
+	 * @param string $context
6283
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6284
+	 * @throws EE_Error
6285
+	 */
6286
+	public function cap_action_for_context($context)
6287
+	{
6288
+		$mapping = $this->cap_contexts_to_cap_action_map();
6289
+		if (isset($mapping[ $context ])) {
6290
+			return $mapping[ $context ];
6291
+		}
6292
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6293
+			return $action;
6294
+		}
6295
+		throw new EE_Error(
6296
+			sprintf(
6297
+				esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6298
+				$context,
6299
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6300
+			)
6301
+		);
6302
+	}
6303
+
6304
+
6305
+	/**
6306
+	 * Returns all the capability contexts which are valid when querying models
6307
+	 *
6308
+	 * @return array
6309
+	 */
6310
+	public static function valid_cap_contexts()
6311
+	{
6312
+		return apply_filters(
6313
+			'FHEE__EEM_Base__valid_cap_contexts',
6314
+			[
6315
+				self::caps_read,
6316
+				self::caps_read_admin,
6317
+				self::caps_edit,
6318
+				self::caps_delete,
6319
+			]
6320
+		);
6321
+	}
6322
+
6323
+
6324
+	/**
6325
+	 * Returns all valid options for 'default_where_conditions'
6326
+	 *
6327
+	 * @return array
6328
+	 */
6329
+	public static function valid_default_where_conditions()
6330
+	{
6331
+		return [
6332
+			EEM_Base::default_where_conditions_all,
6333
+			EEM_Base::default_where_conditions_this_only,
6334
+			EEM_Base::default_where_conditions_others_only,
6335
+			EEM_Base::default_where_conditions_minimum_all,
6336
+			EEM_Base::default_where_conditions_minimum_others,
6337
+			EEM_Base::default_where_conditions_none,
6338
+		];
6339
+	}
6340
+
6341
+	// public static function default_where_conditions_full
6342
+
6343
+
6344
+	/**
6345
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6346
+	 *
6347
+	 * @param string $context
6348
+	 * @return bool
6349
+	 * @throws EE_Error
6350
+	 */
6351
+	public static function verify_is_valid_cap_context($context)
6352
+	{
6353
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6354
+		if (in_array($context, $valid_cap_contexts)) {
6355
+			return true;
6356
+		}
6357
+		throw new EE_Error(
6358
+			sprintf(
6359
+				esc_html__(
6360
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6361
+					'event_espresso'
6362
+				),
6363
+				$context,
6364
+				'EEM_Base',
6365
+				implode(',', $valid_cap_contexts)
6366
+			)
6367
+		);
6368
+	}
6369
+
6370
+
6371
+	/**
6372
+	 * Clears all the models field caches. This is only useful when a sub-class
6373
+	 * might have added a field or something and these caches might be invalidated
6374
+	 */
6375
+	protected function _invalidate_field_caches()
6376
+	{
6377
+		$this->_cache_foreign_key_to_fields = [];
6378
+		$this->_cached_fields               = null;
6379
+		$this->_cached_fields_non_db_only   = null;
6380
+	}
6381
+
6382
+
6383
+	/**
6384
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6385
+	 * (eg "and", "or", "not").
6386
+	 *
6387
+	 * @return array
6388
+	 */
6389
+	public function logic_query_param_keys()
6390
+	{
6391
+		return $this->_logic_query_param_keys;
6392
+	}
6393
+
6394
+
6395
+	/**
6396
+	 * Determines whether or not the where query param array key is for a logic query param.
6397
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6398
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6399
+	 *
6400
+	 * @param $query_param_key
6401
+	 * @return bool
6402
+	 */
6403
+	public function is_logic_query_param_key($query_param_key)
6404
+	{
6405
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6406
+			if (
6407
+				$query_param_key === $logic_query_param_key
6408
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6409
+			) {
6410
+				return true;
6411
+			}
6412
+		}
6413
+		return false;
6414
+	}
6415
+
6416
+
6417
+	/**
6418
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6419
+	 *
6420
+	 * @return boolean
6421
+	 * @since 4.9.74.p
6422
+	 */
6423
+	public function hasPassword()
6424
+	{
6425
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6426
+		if ($this->has_password_field === null) {
6427
+			$password_field           = $this->getPasswordField();
6428
+			$this->has_password_field = $password_field instanceof EE_Password_Field;
6429
+		}
6430
+		return $this->has_password_field;
6431
+	}
6432
+
6433
+
6434
+	/**
6435
+	 * Returns the password field on this model, if there is one
6436
+	 *
6437
+	 * @return EE_Password_Field|null
6438
+	 * @since 4.9.74.p
6439
+	 */
6440
+	public function getPasswordField()
6441
+	{
6442
+		// if we definitely already know there is a password field or not (because has_password_field is true or false)
6443
+		// there's no need to search for it. If we don't know yet, then find out
6444
+		if ($this->has_password_field === null && $this->password_field === null) {
6445
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6446
+		}
6447
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6448
+		return $this->password_field;
6449
+	}
6450
+
6451
+
6452
+	/**
6453
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6454
+	 *
6455
+	 * @return EE_Model_Field_Base[]
6456
+	 * @throws EE_Error
6457
+	 * @since 4.9.74.p
6458
+	 */
6459
+	public function getPasswordProtectedFields()
6460
+	{
6461
+		$password_field = $this->getPasswordField();
6462
+		$fields         = [];
6463
+		if ($password_field instanceof EE_Password_Field) {
6464
+			$field_names = $password_field->protectedFields();
6465
+			foreach ($field_names as $field_name) {
6466
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6467
+			}
6468
+		}
6469
+		return $fields;
6470
+	}
6471
+
6472
+
6473
+	/**
6474
+	 * Checks if the current user can perform the requested action on this model
6475
+	 *
6476
+	 * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6477
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6478
+	 * @return bool
6479
+	 * @throws EE_Error
6480
+	 * @throws InvalidArgumentException
6481
+	 * @throws InvalidDataTypeException
6482
+	 * @throws InvalidInterfaceException
6483
+	 * @throws ReflectionException
6484
+	 * @throws UnexpectedEntityException
6485
+	 * @since 4.9.74.p
6486
+	 */
6487
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6488
+	{
6489
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6490
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6491
+		}
6492
+		if (! is_array($model_obj_or_fields_n_values)) {
6493
+			throw new UnexpectedEntityException(
6494
+				$model_obj_or_fields_n_values,
6495
+				'EE_Base_Class',
6496
+				sprintf(
6497
+					esc_html__(
6498
+						'%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6499
+						'event_espresso'
6500
+					),
6501
+					__FUNCTION__
6502
+				)
6503
+			);
6504
+		}
6505
+		return $this->exists(
6506
+			$this->alter_query_params_to_restrict_by_ID(
6507
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6508
+				[
6509
+					'default_where_conditions' => 'none',
6510
+					'caps'                     => $cap_to_check,
6511
+				]
6512
+			)
6513
+		);
6514
+	}
6515
+
6516
+
6517
+	/**
6518
+	 * Returns the query param where conditions key to the password affecting this model.
6519
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6520
+	 *
6521
+	 * @return null|string
6522
+	 * @throws EE_Error
6523
+	 * @throws InvalidArgumentException
6524
+	 * @throws InvalidDataTypeException
6525
+	 * @throws InvalidInterfaceException
6526
+	 * @throws ModelConfigurationException
6527
+	 * @throws ReflectionException
6528
+	 * @since 4.9.74.p
6529
+	 */
6530
+	public function modelChainAndPassword()
6531
+	{
6532
+		if ($this->model_chain_to_password === null) {
6533
+			throw new ModelConfigurationException(
6534
+				$this,
6535
+				esc_html_x(
6536
+				// @codingStandardsIgnoreStart
6537
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6538
+					// @codingStandardsIgnoreEnd
6539
+					'1: model name',
6540
+					'event_espresso'
6541
+				)
6542
+			);
6543
+		}
6544
+		if ($this->model_chain_to_password === '') {
6545
+			$model_with_password = $this;
6546
+		} else {
6547
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6548
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6549
+			} else {
6550
+				$last_model_in_chain = $this->model_chain_to_password;
6551
+			}
6552
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6553
+		}
6554
+
6555
+		$password_field = $model_with_password->getPasswordField();
6556
+		if ($password_field instanceof EE_Password_Field) {
6557
+			$password_field_name = $password_field->get_name();
6558
+		} else {
6559
+			throw new ModelConfigurationException(
6560
+				$this,
6561
+				sprintf(
6562
+					esc_html_x(
6563
+						'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"',
6564
+						'1: model name, 2: special string',
6565
+						'event_espresso'
6566
+					),
6567
+					$model_with_password->get_this_model_name(),
6568
+					$this->model_chain_to_password
6569
+				)
6570
+			);
6571
+		}
6572
+		return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6573
+	}
6574
+
6575
+
6576
+	/**
6577
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6578
+	 * or if this model itself has a password affecting access to some of its other fields.
6579
+	 *
6580
+	 * @return boolean
6581
+	 * @since 4.9.74.p
6582
+	 */
6583
+	public function restrictedByRelatedModelPassword()
6584
+	{
6585
+		return $this->model_chain_to_password !== null;
6586
+	}
6587 6587
 }
Please login to merge, or discard this patch.
Spacing   +244 added lines, -244 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;
@@ -1478,7 +1478,7 @@  discard block
 block discarded – undo
1478 1478
     {
1479 1479
         $field_settings = $this->field_settings_for($field_name);
1480 1480
         // if not a valid EE_Datetime_Field then throw error
1481
-        if (! $field_settings instanceof EE_Datetime_Field) {
1481
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1482 1482
             throw new EE_Error(
1483 1483
                 sprintf(
1484 1484
                     esc_html__(
@@ -1629,7 +1629,7 @@  discard block
 block discarded – undo
1629 1629
      */
1630 1630
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1631 1631
     {
1632
-        if (! is_array($query_params)) {
1632
+        if ( ! is_array($query_params)) {
1633 1633
             EE_Error::doing_it_wrong(
1634 1634
                 'EEM_Base::update',
1635 1635
                 sprintf(
@@ -1658,7 +1658,7 @@  discard block
 block discarded – undo
1658 1658
          * @param array    $query_params
1659 1659
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1660 1660
          */
1661
-        $fields_n_values = (array)apply_filters(
1661
+        $fields_n_values = (array) apply_filters(
1662 1662
             'FHEE__EEM_Base__update__fields_n_values',
1663 1663
             $fields_n_values,
1664 1664
             $this,
@@ -1676,10 +1676,10 @@  discard block
 block discarded – undo
1676 1676
         $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1677 1677
         foreach ($wpdb_select_results as $wpdb_result) {
1678 1678
             // type cast stdClass as array
1679
-            $wpdb_result = (array)$wpdb_result;
1679
+            $wpdb_result = (array) $wpdb_result;
1680 1680
             // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1681 1681
             if ($this->has_primary_key_field()) {
1682
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1682
+                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1683 1683
             } else {
1684 1684
                 // 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)
1685 1685
                 $main_table_pk_value = null;
@@ -1695,7 +1695,7 @@  discard block
 block discarded – undo
1695 1695
                     // in this table, right? so insert a row in the current table, using any fields available
1696 1696
                     if (
1697 1697
                     ! (array_key_exists($this_table_pk_column, $wpdb_result)
1698
-                       && $wpdb_result[ $this_table_pk_column ])
1698
+                       && $wpdb_result[$this_table_pk_column])
1699 1699
                     ) {
1700 1700
                         $success = $this->_insert_into_specific_table(
1701 1701
                             $table_obj,
@@ -1703,7 +1703,7 @@  discard block
 block discarded – undo
1703 1703
                             $main_table_pk_value
1704 1704
                         );
1705 1705
                         // if we died here, report the error
1706
-                        if (! $success) {
1706
+                        if ( ! $success) {
1707 1707
                             return false;
1708 1708
                         }
1709 1709
                     }
@@ -1731,10 +1731,10 @@  discard block
 block discarded – undo
1731 1731
                 $model_objs_affected_ids     = [];
1732 1732
                 foreach ($models_affected_key_columns as $row) {
1733 1733
                     $combined_index_key                             = $this->get_index_primary_key_string($row);
1734
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1734
+                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1735 1735
                 }
1736 1736
             }
1737
-            if (! $model_objs_affected_ids) {
1737
+            if ( ! $model_objs_affected_ids) {
1738 1738
                 // wait wait wait- if nothing was affected let's stop here
1739 1739
                 return 0;
1740 1740
             }
@@ -1762,8 +1762,8 @@  discard block
 block discarded – undo
1762 1762
                             . " SET "
1763 1763
                             . $this->_construct_update_sql($fields_n_values)
1764 1764
                             . $model_query_info->get_where_sql(
1765
-            );// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1766
-        $rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1765
+            ); // note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1766
+        $rows_affected = $this->_do_wpdb_query('query', [$SQL]);
1767 1767
         /**
1768 1768
          * Action called after a model update call has been made.
1769 1769
          *
@@ -1774,7 +1774,7 @@  discard block
 block discarded – undo
1774 1774
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1775 1775
          */
1776 1776
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1777
-        return $rows_affected;// how many supposedly got updated
1777
+        return $rows_affected; // how many supposedly got updated
1778 1778
     }
1779 1779
 
1780 1780
 
@@ -1805,7 +1805,7 @@  discard block
 block discarded – undo
1805 1805
         $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1806 1806
         $select_expressions = $field->get_qualified_column();
1807 1807
         $SQL                =
1808
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1808
+            "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1809 1809
         return $this->_do_wpdb_query('get_col', [$SQL]);
1810 1810
     }
1811 1811
 
@@ -1825,7 +1825,7 @@  discard block
 block discarded – undo
1825 1825
     {
1826 1826
         $query_params['limit'] = 1;
1827 1827
         $col                   = $this->get_col($query_params, $field_to_select);
1828
-        if (! empty($col)) {
1828
+        if ( ! empty($col)) {
1829 1829
             return reset($col);
1830 1830
         }
1831 1831
         return null;
@@ -1854,7 +1854,7 @@  discard block
 block discarded – undo
1854 1854
             $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1855 1855
             $value_sql       = $prepared_value === null ? 'NULL'
1856 1856
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1857
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1857
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1858 1858
         }
1859 1859
         return implode(",", $cols_n_values);
1860 1860
     }
@@ -1990,7 +1990,7 @@  discard block
 block discarded – undo
1990 1990
                                 . $model_query_info->get_full_join_sql()
1991 1991
                                 . " WHERE "
1992 1992
                                 . $deletion_where_query_part;
1993
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
1993
+            $rows_deleted = $this->_do_wpdb_query('query', [$SQL]);
1994 1994
         } else {
1995 1995
             $rows_deleted = 0;
1996 1996
         }
@@ -2000,12 +2000,12 @@  discard block
 block discarded – undo
2000 2000
         if (
2001 2001
             $this->has_primary_key_field()
2002 2002
             && $rows_deleted !== false
2003
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2003
+            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2004 2004
         ) {
2005
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2005
+            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2006 2006
             foreach ($ids_for_removal as $id) {
2007
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2008
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2007
+                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2008
+                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2009 2009
                 }
2010 2010
             }
2011 2011
 
@@ -2045,7 +2045,7 @@  discard block
 block discarded – undo
2045 2045
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2046 2046
          */
2047 2047
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2048
-        return $rows_deleted;// how many supposedly got deleted
2048
+        return $rows_deleted; // how many supposedly got deleted
2049 2049
     }
2050 2050
 
2051 2051
 
@@ -2141,15 +2141,15 @@  discard block
 block discarded – undo
2141 2141
                 if (
2142 2142
                     $allow_blocking
2143 2143
                     && $this->delete_is_blocked_by_related_models(
2144
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2144
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2145 2145
                     )
2146 2146
                 ) {
2147 2147
                     continue;
2148 2148
                 }
2149 2149
                 // primary table deletes
2150
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2151
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2152
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2150
+                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2151
+                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2152
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2153 2153
                 }
2154 2154
             }
2155 2155
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2158,8 +2158,8 @@  discard block
 block discarded – undo
2158 2158
                 $ids_to_delete_indexed_by_column_for_row = [];
2159 2159
                 foreach ($fields as $cpk_field) {
2160 2160
                     if ($cpk_field instanceof EE_Model_Field_Base) {
2161
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2162
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2161
+                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2162
+                            $item_to_delete[$cpk_field->get_qualified_column()];
2163 2163
                     }
2164 2164
                 }
2165 2165
                 $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
@@ -2199,7 +2199,7 @@  discard block
 block discarded – undo
2199 2199
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2200 2200
                 // make sure we have unique $ids
2201 2201
                 $ids     = array_unique($ids);
2202
-                $query[] = $column . ' IN(' . implode(',', $ids) . ')';
2202
+                $query[] = $column.' IN('.implode(',', $ids).')';
2203 2203
             }
2204 2204
             $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2205 2205
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2207,7 +2207,7 @@  discard block
 block discarded – undo
2207 2207
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2208 2208
                 $values_for_each_combined_primary_key_for_a_row = [];
2209 2209
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2210
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2210
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2211 2211
                 }
2212 2212
                 $ways_to_identify_a_row[] = '('
2213 2213
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2281,10 +2281,10 @@  discard block
 block discarded – undo
2281 2281
                 $column_to_count = '*';
2282 2282
             }
2283 2283
         }
2284
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2284
+        $column_to_count = $distinct ? "DISTINCT ".$column_to_count : $column_to_count;
2285 2285
         $SQL             =
2286
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2287
-        return (int)$this->_do_wpdb_query('get_var', [$SQL]);
2286
+            "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2287
+        return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2288 2288
     }
2289 2289
 
2290 2290
 
@@ -2308,14 +2308,14 @@  discard block
 block discarded – undo
2308 2308
         }
2309 2309
         $column_to_count = $field_obj->get_qualified_column();
2310 2310
         $SQL             =
2311
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2311
+            "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2312 2312
         $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2313 2313
         $data_type       = $field_obj->get_wpdb_data_type();
2314 2314
         if ($data_type === '%d' || $data_type === '%s') {
2315
-            return (float)$return_value;
2315
+            return (float) $return_value;
2316 2316
         }
2317 2317
         // must be %f
2318
-        return (float)$return_value;
2318
+        return (float) $return_value;
2319 2319
     }
2320 2320
 
2321 2321
 
@@ -2334,7 +2334,7 @@  discard block
 block discarded – undo
2334 2334
         // if we're in maintenance mode level 2, DON'T run any queries
2335 2335
         // because level 2 indicates the database needs updating and
2336 2336
         // is probably out of sync with the code
2337
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2337
+        if ( ! EE_Maintenance_Mode::instance()->models_can_query()) {
2338 2338
             throw new EE_Error(
2339 2339
                 sprintf(
2340 2340
                     esc_html__(
@@ -2345,7 +2345,7 @@  discard block
 block discarded – undo
2345 2345
             );
2346 2346
         }
2347 2347
         global $wpdb;
2348
-        if (! method_exists($wpdb, $wpdb_method)) {
2348
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2349 2349
             throw new EE_Error(
2350 2350
                 sprintf(
2351 2351
                     esc_html__(
@@ -2364,7 +2364,7 @@  discard block
 block discarded – undo
2364 2364
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2365 2365
         if (WP_DEBUG) {
2366 2366
             $wpdb->show_errors($old_show_errors_value);
2367
-            if (! empty($wpdb->last_error)) {
2367
+            if ( ! empty($wpdb->last_error)) {
2368 2368
                 throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2369 2369
             }
2370 2370
             if ($result === false) {
@@ -2434,7 +2434,7 @@  discard block
 block discarded – undo
2434 2434
                     // ummmm... you in trouble
2435 2435
                     return $result;
2436 2436
             }
2437
-            if (! empty($error_message)) {
2437
+            if ( ! empty($error_message)) {
2438 2438
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2439 2439
                 trigger_error($error_message);
2440 2440
             }
@@ -2511,11 +2511,11 @@  discard block
 block discarded – undo
2511 2511
      */
2512 2512
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2513 2513
     {
2514
-        return " FROM " . $model_query_info->get_full_join_sql() .
2515
-               $model_query_info->get_where_sql() .
2516
-               $model_query_info->get_group_by_sql() .
2517
-               $model_query_info->get_having_sql() .
2518
-               $model_query_info->get_order_by_sql() .
2514
+        return " FROM ".$model_query_info->get_full_join_sql().
2515
+               $model_query_info->get_where_sql().
2516
+               $model_query_info->get_group_by_sql().
2517
+               $model_query_info->get_having_sql().
2518
+               $model_query_info->get_order_by_sql().
2519 2519
                $model_query_info->get_limit_sql();
2520 2520
     }
2521 2521
 
@@ -2708,12 +2708,12 @@  discard block
 block discarded – undo
2708 2708
         $related_model = $this->get_related_model_obj($model_name);
2709 2709
         // we're just going to use the query params on the related model's normal get_all query,
2710 2710
         // except add a condition to say to match the current mod
2711
-        if (! isset($query_params['default_where_conditions'])) {
2711
+        if ( ! isset($query_params['default_where_conditions'])) {
2712 2712
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2713 2713
         }
2714 2714
         $this_model_name                                                 = $this->get_this_model_name();
2715 2715
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2716
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2716
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2717 2717
         return $related_model->count($query_params, $field_to_count, $distinct);
2718 2718
     }
2719 2719
 
@@ -2734,7 +2734,7 @@  discard block
 block discarded – undo
2734 2734
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2735 2735
     {
2736 2736
         $related_model = $this->get_related_model_obj($model_name);
2737
-        if (! is_array($query_params)) {
2737
+        if ( ! is_array($query_params)) {
2738 2738
             EE_Error::doing_it_wrong(
2739 2739
                 'EEM_Base::sum_related',
2740 2740
                 sprintf(
@@ -2747,12 +2747,12 @@  discard block
 block discarded – undo
2747 2747
         }
2748 2748
         // we're just going to use the query params on the related model's normal get_all query,
2749 2749
         // except add a condition to say to match the current mod
2750
-        if (! isset($query_params['default_where_conditions'])) {
2750
+        if ( ! isset($query_params['default_where_conditions'])) {
2751 2751
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2752 2752
         }
2753 2753
         $this_model_name                                                 = $this->get_this_model_name();
2754 2754
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2755
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2755
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2756 2756
         return $related_model->sum($query_params, $field_to_sum);
2757 2757
     }
2758 2758
 
@@ -2804,7 +2804,7 @@  discard block
 block discarded – undo
2804 2804
                 $field_with_model_name = $field;
2805 2805
             }
2806 2806
         }
2807
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2807
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2808 2808
             throw new EE_Error(
2809 2809
                 sprintf(
2810 2810
                     esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
@@ -2841,7 +2841,7 @@  discard block
 block discarded – undo
2841 2841
          * @param array    $fields_n_values keys are the fields and values are their new values
2842 2842
          * @param EEM_Base $model           the model used
2843 2843
          */
2844
-        $field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2844
+        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2845 2845
         if ($this->_satisfies_unique_indexes($field_n_values)) {
2846 2846
             $main_table = $this->_get_main_table();
2847 2847
             $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
@@ -2943,14 +2943,14 @@  discard block
 block discarded – undo
2943 2943
                 || $this->get_primary_key_field()
2944 2944
                    instanceof
2945 2945
                    EE_Primary_Key_String_Field)
2946
-            && isset($fields_n_values[ $this->primary_key_name() ])
2946
+            && isset($fields_n_values[$this->primary_key_name()])
2947 2947
         ) {
2948
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2948
+            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
2949 2949
         }
2950 2950
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2951 2951
             $uniqueness_where_params                              =
2952 2952
                 array_intersect_key($fields_n_values, $unique_index->fields());
2953
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2953
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
2954 2954
         }
2955 2955
         // if there is nothing to base this search on, then we shouldn't find anything
2956 2956
         if (empty($query_params)) {
@@ -3027,16 +3027,16 @@  discard block
 block discarded – undo
3027 3027
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3028 3028
             // if the value we want to assign it to is NULL, just don't mention it for the insertion
3029 3029
             if ($prepared_value !== null) {
3030
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3030
+                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3031 3031
                 $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3032 3032
             }
3033 3033
         }
3034 3034
         if ($table instanceof EE_Secondary_Table && $new_id) {
3035 3035
             // its not the main table, so we should have already saved the main table's PK which we just inserted
3036 3036
             // so add the fk to the main table as a column
3037
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3037
+            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3038 3038
             $format_for_insertion[]                              =
3039
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3039
+                '%d'; // yes right now we're only allowing these foreign keys to be INTs
3040 3040
         }
3041 3041
         // insert the new entry
3042 3042
         $result = $this->_do_wpdb_query(
@@ -3053,7 +3053,7 @@  discard block
 block discarded – undo
3053 3053
             }
3054 3054
             // it's not an auto-increment primary key, so
3055 3055
             // it must have been supplied
3056
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3056
+            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3057 3057
         }
3058 3058
         // we can't return a  primary key because there is none. instead return
3059 3059
         // a unique string indicating this model
@@ -3077,14 +3077,14 @@  discard block
 block discarded – undo
3077 3077
         if (
3078 3078
             ! $field_obj->is_nullable()
3079 3079
             && (
3080
-                ! isset($fields_n_values[ $field_obj->get_name() ])
3081
-                || $fields_n_values[ $field_obj->get_name() ] === null
3080
+                ! isset($fields_n_values[$field_obj->get_name()])
3081
+                || $fields_n_values[$field_obj->get_name()] === null
3082 3082
             )
3083 3083
         ) {
3084
-            $fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3084
+            $fields_n_values[$field_obj->get_name()] = $field_obj->get_default_value();
3085 3085
         }
3086
-        $unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3087
-            ? $fields_n_values[ $field_obj->get_name() ]
3086
+        $unprepared_value = isset($fields_n_values[$field_obj->get_name()])
3087
+            ? $fields_n_values[$field_obj->get_name()]
3088 3088
             : null;
3089 3089
         return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3090 3090
     }
@@ -3185,7 +3185,7 @@  discard block
 block discarded – undo
3185 3185
      */
3186 3186
     public function get_table_obj_by_alias($table_alias = '')
3187 3187
     {
3188
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3188
+        return isset($this->_tables[$table_alias]) ? $this->_tables[$table_alias] : null;
3189 3189
     }
3190 3190
 
3191 3191
 
@@ -3199,7 +3199,7 @@  discard block
 block discarded – undo
3199 3199
         $other_tables = [];
3200 3200
         foreach ($this->_tables as $table_alias => $table) {
3201 3201
             if ($table instanceof EE_Secondary_Table) {
3202
-                $other_tables[ $table_alias ] = $table;
3202
+                $other_tables[$table_alias] = $table;
3203 3203
             }
3204 3204
         }
3205 3205
         return $other_tables;
@@ -3214,7 +3214,7 @@  discard block
 block discarded – undo
3214 3214
      */
3215 3215
     public function _get_fields_for_table($table_alias)
3216 3216
     {
3217
-        return $this->_fields[ $table_alias ];
3217
+        return $this->_fields[$table_alias];
3218 3218
     }
3219 3219
 
3220 3220
 
@@ -3243,7 +3243,7 @@  discard block
 block discarded – undo
3243 3243
                     $query_info_carrier,
3244 3244
                     'group_by'
3245 3245
                 );
3246
-            } elseif (! empty($query_params['group_by'])) {
3246
+            } elseif ( ! empty($query_params['group_by'])) {
3247 3247
                 $this->_extract_related_model_info_from_query_param(
3248 3248
                     $query_params['group_by'],
3249 3249
                     $query_info_carrier,
@@ -3265,7 +3265,7 @@  discard block
 block discarded – undo
3265 3265
                     $query_info_carrier,
3266 3266
                     'order_by'
3267 3267
                 );
3268
-            } elseif (! empty($query_params['order_by'])) {
3268
+            } elseif ( ! empty($query_params['order_by'])) {
3269 3269
                 $this->_extract_related_model_info_from_query_param(
3270 3270
                     $query_params['order_by'],
3271 3271
                     $query_info_carrier,
@@ -3300,8 +3300,8 @@  discard block
 block discarded – undo
3300 3300
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3301 3301
         $query_param_type
3302 3302
     ) {
3303
-        if (! empty($sub_query_params)) {
3304
-            $sub_query_params = (array)$sub_query_params;
3303
+        if ( ! empty($sub_query_params)) {
3304
+            $sub_query_params = (array) $sub_query_params;
3305 3305
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3306 3306
                 // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3307 3307
                 $this->_extract_related_model_info_from_query_param(
@@ -3316,7 +3316,7 @@  discard block
 block discarded – undo
3316 3316
                 $query_param_sans_stars =
3317 3317
                     $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3318 3318
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3319
-                    if (! is_array($possibly_array_of_params)) {
3319
+                    if ( ! is_array($possibly_array_of_params)) {
3320 3320
                         throw new EE_Error(
3321 3321
                             sprintf(
3322 3322
                                 esc_html__(
@@ -3342,7 +3342,7 @@  discard block
 block discarded – undo
3342 3342
                     // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3343 3343
                     // indicating that $possible_array_of_params[1] is actually a field name,
3344 3344
                     // from which we should extract query parameters!
3345
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3345
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3346 3346
                         throw new EE_Error(
3347 3347
                             sprintf(
3348 3348
                                 esc_html__(
@@ -3382,8 +3382,8 @@  discard block
 block discarded – undo
3382 3382
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3383 3383
         $query_param_type
3384 3384
     ) {
3385
-        if (! empty($sub_query_params)) {
3386
-            if (! is_array($sub_query_params)) {
3385
+        if ( ! empty($sub_query_params)) {
3386
+            if ( ! is_array($sub_query_params)) {
3387 3387
                 throw new EE_Error(
3388 3388
                     sprintf(
3389 3389
                         esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
@@ -3421,7 +3421,7 @@  discard block
 block discarded – undo
3421 3421
      */
3422 3422
     public function _create_model_query_info_carrier($query_params)
3423 3423
     {
3424
-        if (! is_array($query_params)) {
3424
+        if ( ! is_array($query_params)) {
3425 3425
             EE_Error::doing_it_wrong(
3426 3426
                 'EEM_Base::_create_model_query_info_carrier',
3427 3427
                 sprintf(
@@ -3454,7 +3454,7 @@  discard block
 block discarded – undo
3454 3454
             // only include if related to a cpt where no password has been set
3455 3455
             $query_params[0]['OR*nopassword'] = [
3456 3456
                 $where_param_key_for_password       => '',
3457
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3457
+                $where_param_key_for_password.'*' => ['IS_NULL'],
3458 3458
             ];
3459 3459
         }
3460 3460
         $query_object = $this->_extract_related_models_from_query($query_params);
@@ -3508,7 +3508,7 @@  discard block
 block discarded – undo
3508 3508
         // set limit
3509 3509
         if (array_key_exists('limit', $query_params)) {
3510 3510
             if (is_array($query_params['limit'])) {
3511
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3511
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3512 3512
                     $e = sprintf(
3513 3513
                         esc_html__(
3514 3514
                             "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)",
@@ -3516,12 +3516,12 @@  discard block
 block discarded – undo
3516 3516
                         ),
3517 3517
                         http_build_query($query_params['limit'])
3518 3518
                     );
3519
-                    throw new EE_Error($e . "|" . $e);
3519
+                    throw new EE_Error($e."|".$e);
3520 3520
                 }
3521 3521
                 // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3522
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3523
-            } elseif (! empty($query_params['limit'])) {
3524
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3522
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3523
+            } elseif ( ! empty($query_params['limit'])) {
3524
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3525 3525
             }
3526 3526
         }
3527 3527
         // set order by
@@ -3553,10 +3553,10 @@  discard block
 block discarded – undo
3553 3553
                 $order_array = [];
3554 3554
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3555 3555
                     $order         = $this->_extract_order($order);
3556
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3556
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3557 3557
                 }
3558
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3559
-            } elseif (! empty($query_params['order_by'])) {
3558
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3559
+            } elseif ( ! empty($query_params['order_by'])) {
3560 3560
                 $this->_extract_related_model_info_from_query_param(
3561 3561
                     $query_params['order_by'],
3562 3562
                     $query_object,
@@ -3567,7 +3567,7 @@  discard block
 block discarded – undo
3567 3567
                     ? $this->_extract_order($query_params['order'])
3568 3568
                     : 'DESC';
3569 3569
                 $query_object->set_order_by_sql(
3570
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3570
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3571 3571
                 );
3572 3572
             }
3573 3573
         }
@@ -3579,7 +3579,7 @@  discard block
 block discarded – undo
3579 3579
         ) {
3580 3580
             $pk_field = $this->get_primary_key_field();
3581 3581
             $order    = $this->_extract_order($query_params['order']);
3582
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3582
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3583 3583
         }
3584 3584
         // set group by
3585 3585
         if (array_key_exists('group_by', $query_params)) {
@@ -3589,10 +3589,10 @@  discard block
 block discarded – undo
3589 3589
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3590 3590
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3591 3591
                 }
3592
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3593
-            } elseif (! empty($query_params['group_by'])) {
3592
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3593
+            } elseif ( ! empty($query_params['group_by'])) {
3594 3594
                 $query_object->set_group_by_sql(
3595
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3595
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3596 3596
                 );
3597 3597
             }
3598 3598
         }
@@ -3602,7 +3602,7 @@  discard block
 block discarded – undo
3602 3602
         }
3603 3603
         // now, just verify they didn't pass anything wack
3604 3604
         foreach ($query_params as $query_key => $query_value) {
3605
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3605
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3606 3606
                 throw new EE_Error(
3607 3607
                     sprintf(
3608 3608
                         esc_html__(
@@ -3706,7 +3706,7 @@  discard block
 block discarded – undo
3706 3706
         $where_query_params = []
3707 3707
     ) {
3708 3708
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3709
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3709
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3710 3710
             throw new EE_Error(
3711 3711
                 sprintf(
3712 3712
                     esc_html__(
@@ -3736,7 +3736,7 @@  discard block
 block discarded – undo
3736 3736
                 // we don't want to add full or even minimum default where conditions from this model, so just continue
3737 3737
                 continue;
3738 3738
             }
3739
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3739
+            $overrides = $this->_override_defaults_or_make_null_friendly(
3740 3740
                 $related_model_universal_where_params,
3741 3741
                 $where_query_params,
3742 3742
                 $related_model,
@@ -3845,19 +3845,19 @@  discard block
 block discarded – undo
3845 3845
     ) {
3846 3846
         $null_friendly_where_conditions = [];
3847 3847
         $none_overridden                = true;
3848
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3848
+        $or_condition_key_for_defaults  = 'OR*'.get_class($model);
3849 3849
         foreach ($default_where_conditions as $key => $val) {
3850
-            if (isset($provided_where_conditions[ $key ])) {
3850
+            if (isset($provided_where_conditions[$key])) {
3851 3851
                 $none_overridden = false;
3852 3852
             } else {
3853
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3853
+                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3854 3854
             }
3855 3855
         }
3856 3856
         if ($none_overridden && $default_where_conditions) {
3857 3857
             if ($model->has_primary_key_field()) {
3858
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3858
+                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3859 3859
                                                                                    . "."
3860
-                                                                                   . $model->primary_key_name() ] =
3860
+                                                                                   . $model->primary_key_name()] =
3861 3861
                     ['IS NULL'];
3862 3862
             }/*else{
3863 3863
                 //@todo NO PK, use other defaults
@@ -3966,7 +3966,7 @@  discard block
 block discarded – undo
3966 3966
             foreach ($tables as $table_obj) {
3967 3967
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3968 3968
                                        . $table_obj->get_fully_qualified_pk_column();
3969
-                if (! in_array($qualified_pk_column, $selects)) {
3969
+                if ( ! in_array($qualified_pk_column, $selects)) {
3970 3970
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3971 3971
                 }
3972 3972
             }
@@ -4118,9 +4118,9 @@  discard block
 block discarded – undo
4118 4118
         $query_parameter_type
4119 4119
     ) {
4120 4120
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4121
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4121
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4122 4122
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4123
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4123
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4124 4124
                 if ($possible_join_string === '') {
4125 4125
                     // nothing left to $query_param
4126 4126
                     // we should actually end in a field name, not a model like this!
@@ -4252,7 +4252,7 @@  discard block
 block discarded – undo
4252 4252
     {
4253 4253
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4254 4254
         if ($SQL) {
4255
-            return " WHERE " . $SQL;
4255
+            return " WHERE ".$SQL;
4256 4256
         }
4257 4257
         return '';
4258 4258
     }
@@ -4270,7 +4270,7 @@  discard block
 block discarded – undo
4270 4270
     {
4271 4271
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4272 4272
         if ($SQL) {
4273
-            return " HAVING " . $SQL;
4273
+            return " HAVING ".$SQL;
4274 4274
         }
4275 4275
         return '';
4276 4276
     }
@@ -4293,7 +4293,7 @@  discard block
 block discarded – undo
4293 4293
             $query_param =
4294 4294
                 $this->_remove_stars_and_anything_after_from_condition_query_param_key(
4295 4295
                     $query_param
4296
-                );// str_replace("*",'',$query_param);
4296
+                ); // str_replace("*",'',$query_param);
4297 4297
             if (in_array($query_param, $this->_logic_query_param_keys)) {
4298 4298
                 switch ($query_param) {
4299 4299
                     case 'not':
@@ -4327,7 +4327,7 @@  discard block
 block discarded – undo
4327 4327
             } else {
4328 4328
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4329 4329
                 // if it's not a normal field, maybe it's a custom selection?
4330
-                if (! $field_obj) {
4330
+                if ( ! $field_obj) {
4331 4331
                     if ($this->_custom_selections instanceof CustomSelects) {
4332 4332
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4333 4333
                     } else {
@@ -4343,7 +4343,7 @@  discard block
 block discarded – undo
4343 4343
                     }
4344 4344
                 }
4345 4345
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4346
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4346
+                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4347 4347
             }
4348 4348
         }
4349 4349
         return $where_clauses ? implode($glue, $where_clauses) : '';
@@ -4365,7 +4365,7 @@  discard block
 block discarded – undo
4365 4365
                 $field->get_model_name(),
4366 4366
                 $query_param
4367 4367
             );
4368
-            return $table_alias_prefix . $field->get_qualified_column();
4368
+            return $table_alias_prefix.$field->get_qualified_column();
4369 4369
         }
4370 4370
         if (
4371 4371
             $this->_custom_selections instanceof CustomSelects
@@ -4422,7 +4422,7 @@  discard block
 block discarded – undo
4422 4422
     {
4423 4423
         if (is_array($op_and_value)) {
4424 4424
             $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4425
-            if (! $operator) {
4425
+            if ( ! $operator) {
4426 4426
                 $php_array_like_string = [];
4427 4427
                 foreach ($op_and_value as $key => $value) {
4428 4428
                     $php_array_like_string[] = "$key=>$value";
@@ -4444,14 +4444,14 @@  discard block
 block discarded – undo
4444 4444
         }
4445 4445
         // check to see if the value is actually another field
4446 4446
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4447
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4447
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4448 4448
         }
4449 4449
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4450 4450
             // in this case, the value should be an array, or at least a comma-separated list
4451 4451
             // it will need to handle a little differently
4452 4452
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4453 4453
             // note: $cleaned_value has already been run through $wpdb->prepare()
4454
-            return $operator . SP . $cleaned_value;
4454
+            return $operator.SP.$cleaned_value;
4455 4455
         }
4456 4456
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4457 4457
             // the value should be an array with count of two.
@@ -4467,7 +4467,7 @@  discard block
 block discarded – undo
4467 4467
                 );
4468 4468
             }
4469 4469
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4470
-            return $operator . SP . $cleaned_value;
4470
+            return $operator.SP.$cleaned_value;
4471 4471
         }
4472 4472
         if (in_array($operator, $this->valid_null_style_operators())) {
4473 4473
             if ($value !== null) {
@@ -4487,10 +4487,10 @@  discard block
 block discarded – undo
4487 4487
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4488 4488
             // if the operator is 'LIKE', we want to allow percent signs (%) and not
4489 4489
             // remove other junk. So just treat it as a string.
4490
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4490
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4491 4491
         }
4492
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4493
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4492
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4493
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4494 4494
         }
4495 4495
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4496 4496
             throw new EE_Error(
@@ -4504,7 +4504,7 @@  discard block
 block discarded – undo
4504 4504
                 )
4505 4505
             );
4506 4506
         }
4507
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4507
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4508 4508
             throw new EE_Error(
4509 4509
                 sprintf(
4510 4510
                     esc_html__(
@@ -4543,7 +4543,7 @@  discard block
 block discarded – undo
4543 4543
         foreach ($values as $value) {
4544 4544
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4545 4545
         }
4546
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4546
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4547 4547
     }
4548 4548
 
4549 4549
 
@@ -4583,7 +4583,7 @@  discard block
 block discarded – undo
4583 4583
                                 . $main_table->get_table_name()
4584 4584
                                 . " WHERE FALSE";
4585 4585
         }
4586
-        return "(" . implode(",", $cleaned_values) . ")";
4586
+        return "(".implode(",", $cleaned_values).")";
4587 4587
     }
4588 4588
 
4589 4589
 
@@ -4602,7 +4602,7 @@  discard block
 block discarded – undo
4602 4602
                 $this->_prepare_value_for_use_in_db($value, $field_obj)
4603 4603
             );
4604 4604
         } //$field_obj should really just be a data type
4605
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4605
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4606 4606
             throw new EE_Error(
4607 4607
                 sprintf(
4608 4608
                     esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4639,14 +4639,14 @@  discard block
 block discarded – undo
4639 4639
             );
4640 4640
         }
4641 4641
         $number_of_parts       = count($query_param_parts);
4642
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4642
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4643 4643
         if ($number_of_parts === 1) {
4644 4644
             $field_name = $last_query_param_part;
4645 4645
             $model_obj  = $this;
4646 4646
         } else {// $number_of_parts >= 2
4647 4647
             // the last part is the column name, and there are only 2parts. therefore...
4648 4648
             $field_name = $last_query_param_part;
4649
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4649
+            $model_obj  = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4650 4650
         }
4651 4651
         try {
4652 4652
             return $model_obj->field_settings_for($field_name);
@@ -4667,7 +4667,7 @@  discard block
 block discarded – undo
4667 4667
     public function _get_qualified_column_for_field($field_name)
4668 4668
     {
4669 4669
         $all_fields = $this->field_settings();
4670
-        $field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4670
+        $field      = isset($all_fields[$field_name]) ? $all_fields[$field_name] : false;
4671 4671
         if ($field) {
4672 4672
             return $field->get_qualified_column();
4673 4673
         }
@@ -4737,10 +4737,10 @@  discard block
 block discarded – undo
4737 4737
      */
4738 4738
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4739 4739
     {
4740
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4740
+        $table_prefix      = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain) ? '' : '__');
4741 4741
         $qualified_columns = [];
4742 4742
         foreach ($this->field_settings() as $field_name => $field) {
4743
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4743
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4744 4744
         }
4745 4745
         return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4746 4746
     }
@@ -4764,11 +4764,11 @@  discard block
 block discarded – undo
4764 4764
             if ($table_obj instanceof EE_Primary_Table) {
4765 4765
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4766 4766
                     ? $table_obj->get_select_join_limit($limit)
4767
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4767
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4768 4768
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4769 4769
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4770 4770
                     ? $table_obj->get_select_join_limit_join($limit)
4771
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4771
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4772 4772
             }
4773 4773
         }
4774 4774
         return $SQL;
@@ -4838,7 +4838,7 @@  discard block
 block discarded – undo
4838 4838
         foreach ($this->field_settings() as $field_obj) {
4839 4839
             // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4840 4840
             /** @var $field_obj EE_Model_Field_Base */
4841
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4841
+            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4842 4842
         }
4843 4843
         return $data_types;
4844 4844
     }
@@ -4853,8 +4853,8 @@  discard block
 block discarded – undo
4853 4853
      */
4854 4854
     public function get_related_model_obj($model_name)
4855 4855
     {
4856
-        $model_classname = "EEM_" . $model_name;
4857
-        if (! class_exists($model_classname)) {
4856
+        $model_classname = "EEM_".$model_name;
4857
+        if ( ! class_exists($model_classname)) {
4858 4858
             throw new EE_Error(
4859 4859
                 sprintf(
4860 4860
                     esc_html__(
@@ -4866,7 +4866,7 @@  discard block
 block discarded – undo
4866 4866
                 )
4867 4867
             );
4868 4868
         }
4869
-        return call_user_func($model_classname . "::instance");
4869
+        return call_user_func($model_classname."::instance");
4870 4870
     }
4871 4871
 
4872 4872
 
@@ -4893,7 +4893,7 @@  discard block
 block discarded – undo
4893 4893
         $belongs_to_relations = [];
4894 4894
         foreach ($this->relation_settings() as $model_name => $relation_obj) {
4895 4895
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
4896
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4896
+                $belongs_to_relations[$model_name] = $relation_obj;
4897 4897
             }
4898 4898
         }
4899 4899
         return $belongs_to_relations;
@@ -4910,7 +4910,7 @@  discard block
 block discarded – undo
4910 4910
     public function related_settings_for($relation_name)
4911 4911
     {
4912 4912
         $relatedModels = $this->relation_settings();
4913
-        if (! array_key_exists($relation_name, $relatedModels)) {
4913
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
4914 4914
             throw new EE_Error(
4915 4915
                 sprintf(
4916 4916
                     esc_html__(
@@ -4923,7 +4923,7 @@  discard block
 block discarded – undo
4923 4923
                 )
4924 4924
             );
4925 4925
         }
4926
-        return $relatedModels[ $relation_name ];
4926
+        return $relatedModels[$relation_name];
4927 4927
     }
4928 4928
 
4929 4929
 
@@ -4939,7 +4939,7 @@  discard block
 block discarded – undo
4939 4939
     public function field_settings_for($fieldName, $include_db_only_fields = true)
4940 4940
     {
4941 4941
         $fieldSettings = $this->field_settings($include_db_only_fields);
4942
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4942
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
4943 4943
             throw new EE_Error(
4944 4944
                 sprintf(
4945 4945
                     esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
@@ -4948,7 +4948,7 @@  discard block
 block discarded – undo
4948 4948
                 )
4949 4949
             );
4950 4950
         }
4951
-        return $fieldSettings[ $fieldName ];
4951
+        return $fieldSettings[$fieldName];
4952 4952
     }
4953 4953
 
4954 4954
 
@@ -4961,7 +4961,7 @@  discard block
 block discarded – undo
4961 4961
     public function has_field($fieldName)
4962 4962
     {
4963 4963
         $fieldSettings = $this->field_settings(true);
4964
-        if (isset($fieldSettings[ $fieldName ])) {
4964
+        if (isset($fieldSettings[$fieldName])) {
4965 4965
             return true;
4966 4966
         }
4967 4967
         return false;
@@ -4977,7 +4977,7 @@  discard block
 block discarded – undo
4977 4977
     public function has_relation($relation_name)
4978 4978
     {
4979 4979
         $relations = $this->relation_settings();
4980
-        if (isset($relations[ $relation_name ])) {
4980
+        if (isset($relations[$relation_name])) {
4981 4981
             return true;
4982 4982
         }
4983 4983
         return false;
@@ -5013,7 +5013,7 @@  discard block
 block discarded – undo
5013 5013
                     break;
5014 5014
                 }
5015 5015
             }
5016
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5016
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5017 5017
                 throw new EE_Error(
5018 5018
                     sprintf(
5019 5019
                         esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
@@ -5073,17 +5073,17 @@  discard block
 block discarded – undo
5073 5073
      */
5074 5074
     public function get_foreign_key_to($model_name)
5075 5075
     {
5076
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5076
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5077 5077
             foreach ($this->field_settings() as $field) {
5078 5078
                 if (
5079 5079
                     $field instanceof EE_Foreign_Key_Field_Base
5080 5080
                     && in_array($model_name, $field->get_model_names_pointed_to())
5081 5081
                 ) {
5082
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5082
+                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
5083 5083
                     break;
5084 5084
                 }
5085 5085
             }
5086
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5086
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5087 5087
                 throw new EE_Error(
5088 5088
                     sprintf(
5089 5089
                         esc_html__(
@@ -5096,7 +5096,7 @@  discard block
 block discarded – undo
5096 5096
                 );
5097 5097
             }
5098 5098
         }
5099
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5099
+        return $this->_cache_foreign_key_to_fields[$model_name];
5100 5100
     }
5101 5101
 
5102 5102
 
@@ -5112,7 +5112,7 @@  discard block
 block discarded – undo
5112 5112
     {
5113 5113
         $table_alias_sans_model_relation_chain_prefix =
5114 5114
             EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5115
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5115
+        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
5116 5116
     }
5117 5117
 
5118 5118
 
@@ -5130,7 +5130,7 @@  discard block
 block discarded – undo
5130 5130
                 $this->_cached_fields = [];
5131 5131
                 foreach ($this->_fields as $fields_corresponding_to_table) {
5132 5132
                     foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5133
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5133
+                        $this->_cached_fields[$field_name] = $field_obj;
5134 5134
                     }
5135 5135
                 }
5136 5136
             }
@@ -5141,8 +5141,8 @@  discard block
 block discarded – undo
5141 5141
             foreach ($this->_fields as $fields_corresponding_to_table) {
5142 5142
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5143 5143
                     /** @var $field_obj EE_Model_Field_Base */
5144
-                    if (! $field_obj->is_db_only_field()) {
5145
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5144
+                    if ( ! $field_obj->is_db_only_field()) {
5145
+                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5146 5146
                     }
5147 5147
                 }
5148 5148
             }
@@ -5170,7 +5170,7 @@  discard block
 block discarded – undo
5170 5170
         $count_if_model_has_no_primary_key = 0;
5171 5171
         $has_primary_key                   = $this->has_primary_key_field();
5172 5172
         $primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5173
-        foreach ((array)$rows as $row) {
5173
+        foreach ((array) $rows as $row) {
5174 5174
             if (empty($row)) {
5175 5175
                 // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5176 5176
                 return [];
@@ -5183,12 +5183,12 @@  discard block
 block discarded – undo
5183 5183
                     $primary_key_field->get_qualified_column(),
5184 5184
                     $primary_key_field->get_table_column()
5185 5185
                 );
5186
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5186
+                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5187 5187
                     continue;
5188 5188
                 }
5189 5189
             }
5190 5190
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5191
-            if (! $classInstance) {
5191
+            if ( ! $classInstance) {
5192 5192
                 throw new EE_Error(
5193 5193
                     sprintf(
5194 5194
                         esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5201,7 +5201,7 @@  discard block
 block discarded – undo
5201 5201
             $classInstance->set_timezone($this->_timezone);
5202 5202
             // make sure if there is any timezone setting present that we set the timezone for the object
5203 5203
             $key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5204
-            $array_of_objects[ $key ] = $classInstance;
5204
+            $array_of_objects[$key] = $classInstance;
5205 5205
             // also, for all the relations of type BelongsTo, see if we can cache
5206 5206
             // those related models
5207 5207
             // (we could do this for other relations too, but if there are conditions
@@ -5245,9 +5245,9 @@  discard block
 block discarded – undo
5245 5245
         $results = [];
5246 5246
         if ($this->_custom_selections instanceof CustomSelects) {
5247 5247
             foreach ($this->_custom_selections->columnAliases() as $alias) {
5248
-                if (isset($db_results_row[ $alias ])) {
5249
-                    $results[ $alias ] = $this->convertValueToDataType(
5250
-                        $db_results_row[ $alias ],
5248
+                if (isset($db_results_row[$alias])) {
5249
+                    $results[$alias] = $this->convertValueToDataType(
5250
+                        $db_results_row[$alias],
5251 5251
                         $this->_custom_selections->getDataTypeForAlias($alias)
5252 5252
                     );
5253 5253
                 }
@@ -5268,11 +5268,11 @@  discard block
 block discarded – undo
5268 5268
     {
5269 5269
         switch ($datatype) {
5270 5270
             case '%f':
5271
-                return (float)$value;
5271
+                return (float) $value;
5272 5272
             case '%d':
5273
-                return (int)$value;
5273
+                return (int) $value;
5274 5274
             default:
5275
-                return (string)$value;
5275
+                return (string) $value;
5276 5276
         }
5277 5277
     }
5278 5278
 
@@ -5292,7 +5292,7 @@  discard block
 block discarded – undo
5292 5292
         $this_model_fields_and_values = [];
5293 5293
         // setup the row using default values;
5294 5294
         foreach ($this->field_settings() as $field_name => $field_obj) {
5295
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5295
+            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5296 5296
         }
5297 5297
         $className = $this->_get_class_name();
5298 5298
         return EE_Registry::instance()->load_class(
@@ -5313,20 +5313,20 @@  discard block
 block discarded – undo
5313 5313
      */
5314 5314
     public function instantiate_class_from_array_or_object($cols_n_values)
5315 5315
     {
5316
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5316
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5317 5317
             $cols_n_values = get_object_vars($cols_n_values);
5318 5318
         }
5319 5319
         $primary_key = null;
5320 5320
         // make sure the array only has keys that are fields/columns on this model
5321 5321
         $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5322
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5323
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5322
+        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5323
+            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5324 5324
         }
5325 5325
         $className = $this->_get_class_name();
5326 5326
         // check we actually found results that we can use to build our model object
5327 5327
         // if not, return null
5328 5328
         if ($this->has_primary_key_field()) {
5329
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5329
+            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5330 5330
                 return null;
5331 5331
             }
5332 5332
         } elseif ($this->unique_indexes()) {
@@ -5338,7 +5338,7 @@  discard block
 block discarded – undo
5338 5338
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5339 5339
         if ($primary_key) {
5340 5340
             $classInstance = $this->get_from_entity_map($primary_key);
5341
-            if (! $classInstance) {
5341
+            if ( ! $classInstance) {
5342 5342
                 $classInstance = EE_Registry::instance()->load_class(
5343 5343
                     $className,
5344 5344
                     [$this_model_fields_n_values, $this->_timezone],
@@ -5368,8 +5368,8 @@  discard block
 block discarded – undo
5368 5368
      */
5369 5369
     public function get_from_entity_map($id)
5370 5370
     {
5371
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5372
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5371
+        return isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])
5372
+            ? $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] : null;
5373 5373
     }
5374 5374
 
5375 5375
 
@@ -5392,7 +5392,7 @@  discard block
 block discarded – undo
5392 5392
     public function add_to_entity_map(EE_Base_Class $object)
5393 5393
     {
5394 5394
         $className = $this->_get_class_name();
5395
-        if (! $object instanceof $className) {
5395
+        if ( ! $object instanceof $className) {
5396 5396
             throw new EE_Error(
5397 5397
                 sprintf(
5398 5398
                     esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
@@ -5401,7 +5401,7 @@  discard block
 block discarded – undo
5401 5401
                 )
5402 5402
             );
5403 5403
         }
5404
-        if (! $object->ID()) {
5404
+        if ( ! $object->ID()) {
5405 5405
             throw new EE_Error(
5406 5406
                 sprintf(
5407 5407
                     esc_html__(
@@ -5417,7 +5417,7 @@  discard block
 block discarded – undo
5417 5417
         if ($classInstance) {
5418 5418
             return $classInstance;
5419 5419
         }
5420
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5420
+        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5421 5421
         return $object;
5422 5422
     }
5423 5423
 
@@ -5432,11 +5432,11 @@  discard block
 block discarded – undo
5432 5432
     public function clear_entity_map($id = null)
5433 5433
     {
5434 5434
         if (empty($id)) {
5435
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5435
+            $this->_entity_map[EEM_Base::$_model_query_blog_id] = [];
5436 5436
             return true;
5437 5437
         }
5438
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5439
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5438
+        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5439
+            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5440 5440
             return true;
5441 5441
         }
5442 5442
         return false;
@@ -5477,18 +5477,18 @@  discard block
 block discarded – undo
5477 5477
             // there is a primary key on this table and its not set. Use defaults for all its columns
5478 5478
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5479 5479
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5480
-                    if (! $field_obj->is_db_only_field()) {
5480
+                    if ( ! $field_obj->is_db_only_field()) {
5481 5481
                         // prepare field as if its coming from db
5482 5482
                         $prepared_value                            =
5483 5483
                             $field_obj->prepare_for_set($field_obj->get_default_value());
5484
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5484
+                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5485 5485
                     }
5486 5486
                 }
5487 5487
             } else {
5488 5488
                 // the table's rows existed. Use their values
5489 5489
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5490
-                    if (! $field_obj->is_db_only_field()) {
5491
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5490
+                    if ( ! $field_obj->is_db_only_field()) {
5491
+                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5492 5492
                             $cols_n_values,
5493 5493
                             $field_obj->get_qualified_column(),
5494 5494
                             $field_obj->get_table_column()
@@ -5513,17 +5513,17 @@  discard block
 block discarded – undo
5513 5513
         // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5514 5514
         // does the field on the model relate to this column retrieved from the db?
5515 5515
         // or is it a db-only field? (not relating to the model)
5516
-        if (isset($cols_n_values[ $qualified_column ])) {
5517
-            $value = $cols_n_values[ $qualified_column ];
5518
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5519
-            $value = $cols_n_values[ $regular_column ];
5520
-        } elseif (! empty($this->foreign_key_aliases)) {
5516
+        if (isset($cols_n_values[$qualified_column])) {
5517
+            $value = $cols_n_values[$qualified_column];
5518
+        } elseif (isset($cols_n_values[$regular_column])) {
5519
+            $value = $cols_n_values[$regular_column];
5520
+        } elseif ( ! empty($this->foreign_key_aliases)) {
5521 5521
             // no PK?  ok check if there is a foreign key alias set for this table
5522 5522
             // then check if that alias exists in the incoming data
5523 5523
             // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5524 5524
             foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5525
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5526
-                    $value = $cols_n_values[ $FK_alias ];
5525
+                if ($PK_column === $qualified_column && isset($cols_n_values[$FK_alias])) {
5526
+                    $value = $cols_n_values[$FK_alias];
5527 5527
                     break;
5528 5528
                 }
5529 5529
             }
@@ -5559,7 +5559,7 @@  discard block
 block discarded – undo
5559 5559
                     $obj_in_map->clear_cache($relation_name, null, true);
5560 5560
                 }
5561 5561
             }
5562
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5562
+            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5563 5563
             return $obj_in_map;
5564 5564
         }
5565 5565
         return $this->get_one_by_ID($id);
@@ -5611,7 +5611,7 @@  discard block
 block discarded – undo
5611 5611
      */
5612 5612
     private function _get_class_name()
5613 5613
     {
5614
-        return "EE_" . $this->get_this_model_name();
5614
+        return "EE_".$this->get_this_model_name();
5615 5615
     }
5616 5616
 
5617 5617
 
@@ -5625,7 +5625,7 @@  discard block
 block discarded – undo
5625 5625
      */
5626 5626
     public function item_name($quantity = 1)
5627 5627
     {
5628
-        return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5628
+        return (int) $quantity === 1 ? $this->singular_item : $this->plural_item;
5629 5629
     }
5630 5630
 
5631 5631
 
@@ -5657,7 +5657,7 @@  discard block
 block discarded – undo
5657 5657
     {
5658 5658
         $className = get_class($this);
5659 5659
         $tagName   = "FHEE__{$className}__{$methodName}";
5660
-        if (! has_filter($tagName)) {
5660
+        if ( ! has_filter($tagName)) {
5661 5661
             throw new EE_Error(
5662 5662
                 sprintf(
5663 5663
                     esc_html__(
@@ -5827,7 +5827,7 @@  discard block
 block discarded – undo
5827 5827
         $unique_indexes = [];
5828 5828
         foreach ($this->_indexes as $name => $index) {
5829 5829
             if ($index instanceof EE_Unique_Index) {
5830
-                $unique_indexes [ $name ] = $index;
5830
+                $unique_indexes [$name] = $index;
5831 5831
             }
5832 5832
         }
5833 5833
         return $unique_indexes;
@@ -5891,7 +5891,7 @@  discard block
 block discarded – undo
5891 5891
         $key_values_in_combined_pk = [];
5892 5892
         parse_str($index_primary_key_string, $key_values_in_combined_pk);
5893 5893
         foreach ($key_fields as $key_field_name => $field_obj) {
5894
-            if (! isset($key_values_in_combined_pk[ $key_field_name ])) {
5894
+            if ( ! isset($key_values_in_combined_pk[$key_field_name])) {
5895 5895
                 return null;
5896 5896
             }
5897 5897
         }
@@ -5911,7 +5911,7 @@  discard block
 block discarded – undo
5911 5911
     {
5912 5912
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5913 5913
         foreach ($keys_it_should_have as $key) {
5914
-            if (! isset($key_values[ $key ])) {
5914
+            if ( ! isset($key_values[$key])) {
5915 5915
                 return false;
5916 5916
             }
5917 5917
         }
@@ -5950,8 +5950,8 @@  discard block
 block discarded – undo
5950 5950
         }
5951 5951
         // even copies obviously won't have the same ID, so remove the primary key
5952 5952
         // from the WHERE conditions for finding copies (if there is a primary key, of course)
5953
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5954
-            unset($attributes_array[ $this->primary_key_name() ]);
5953
+        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
5954
+            unset($attributes_array[$this->primary_key_name()]);
5955 5955
         }
5956 5956
         if (isset($query_params[0])) {
5957 5957
             $query_params[0] = array_merge($attributes_array, $query_params);
@@ -5973,7 +5973,7 @@  discard block
 block discarded – undo
5973 5973
      */
5974 5974
     public function get_one_copy($model_object_or_attributes_array, $query_params = [])
5975 5975
     {
5976
-        if (! is_array($query_params)) {
5976
+        if ( ! is_array($query_params)) {
5977 5977
             EE_Error::doing_it_wrong(
5978 5978
                 'EEM_Base::get_one_copy',
5979 5979
                 sprintf(
@@ -6023,7 +6023,7 @@  discard block
 block discarded – undo
6023 6023
     private function _prepare_operator_for_sql($operator_supplied)
6024 6024
     {
6025 6025
         $sql_operator =
6026
-            isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6026
+            isset($this->_valid_operators[$operator_supplied]) ? $this->_valid_operators[$operator_supplied]
6027 6027
                 : null;
6028 6028
         if ($sql_operator) {
6029 6029
             return $sql_operator;
@@ -6122,7 +6122,7 @@  discard block
 block discarded – undo
6122 6122
         $objs  = $this->get_all($query_params);
6123 6123
         $names = [];
6124 6124
         foreach ($objs as $obj) {
6125
-            $names[ $obj->ID() ] = $obj->name();
6125
+            $names[$obj->ID()] = $obj->name();
6126 6126
         }
6127 6127
         return $names;
6128 6128
     }
@@ -6143,7 +6143,7 @@  discard block
 block discarded – undo
6143 6143
      */
6144 6144
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
6145 6145
     {
6146
-        if (! $this->has_primary_key_field()) {
6146
+        if ( ! $this->has_primary_key_field()) {
6147 6147
             if (WP_DEBUG) {
6148 6148
                 EE_Error::add_error(
6149 6149
                     esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -6156,7 +6156,7 @@  discard block
 block discarded – undo
6156 6156
         $IDs = [];
6157 6157
         foreach ($model_objects as $model_object) {
6158 6158
             $id = $model_object->ID();
6159
-            if (! $id) {
6159
+            if ( ! $id) {
6160 6160
                 if ($filter_out_empty_ids) {
6161 6161
                     continue;
6162 6162
                 }
@@ -6206,22 +6206,22 @@  discard block
 block discarded – undo
6206 6206
         EEM_Base::verify_is_valid_cap_context($context);
6207 6207
         // check if we ought to run the restriction generator first
6208 6208
         if (
6209
-            isset($this->_cap_restriction_generators[ $context ])
6210
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6211
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6209
+            isset($this->_cap_restriction_generators[$context])
6210
+            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6211
+            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6212 6212
         ) {
6213
-            $this->_cap_restrictions[ $context ] = array_merge(
6214
-                $this->_cap_restrictions[ $context ],
6215
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6213
+            $this->_cap_restrictions[$context] = array_merge(
6214
+                $this->_cap_restrictions[$context],
6215
+                $this->_cap_restriction_generators[$context]->generate_restrictions()
6216 6216
             );
6217 6217
         }
6218 6218
         // and make sure we've finalized the construction of each restriction
6219
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6219
+        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6220 6220
             if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6221 6221
                 $where_conditions_obj->_finalize_construct($this);
6222 6222
             }
6223 6223
         }
6224
-        return $this->_cap_restrictions[ $context ];
6224
+        return $this->_cap_restrictions[$context];
6225 6225
     }
6226 6226
 
6227 6227
 
@@ -6251,9 +6251,9 @@  discard block
 block discarded – undo
6251 6251
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6252 6252
             if (
6253 6253
             ! EE_Capabilities::instance()
6254
-                             ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6254
+                             ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6255 6255
             ) {
6256
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6256
+                $missing_caps[$cap] = $restriction_if_no_cap;
6257 6257
             }
6258 6258
         }
6259 6259
         return $missing_caps;
@@ -6286,8 +6286,8 @@  discard block
 block discarded – undo
6286 6286
     public function cap_action_for_context($context)
6287 6287
     {
6288 6288
         $mapping = $this->cap_contexts_to_cap_action_map();
6289
-        if (isset($mapping[ $context ])) {
6290
-            return $mapping[ $context ];
6289
+        if (isset($mapping[$context])) {
6290
+            return $mapping[$context];
6291 6291
         }
6292 6292
         if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6293 6293
             return $action;
@@ -6405,7 +6405,7 @@  discard block
 block discarded – undo
6405 6405
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6406 6406
             if (
6407 6407
                 $query_param_key === $logic_query_param_key
6408
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6408
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6409 6409
             ) {
6410 6410
                 return true;
6411 6411
             }
@@ -6463,7 +6463,7 @@  discard block
 block discarded – undo
6463 6463
         if ($password_field instanceof EE_Password_Field) {
6464 6464
             $field_names = $password_field->protectedFields();
6465 6465
             foreach ($field_names as $field_name) {
6466
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6466
+                $fields[$field_name] = $this->field_settings_for($field_name);
6467 6467
             }
6468 6468
         }
6469 6469
         return $fields;
@@ -6489,7 +6489,7 @@  discard block
 block discarded – undo
6489 6489
         if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6490 6490
             $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6491 6491
         }
6492
-        if (! is_array($model_obj_or_fields_n_values)) {
6492
+        if ( ! is_array($model_obj_or_fields_n_values)) {
6493 6493
             throw new UnexpectedEntityException(
6494 6494
                 $model_obj_or_fields_n_values,
6495 6495
                 'EE_Base_Class',
@@ -6569,7 +6569,7 @@  discard block
 block discarded – undo
6569 6569
                 )
6570 6570
             );
6571 6571
         }
6572
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6572
+        return ($this->model_chain_to_password ? $this->model_chain_to_password.'.' : '').$password_field_name;
6573 6573
     }
6574 6574
 
6575 6575
 
Please login to merge, or discard this patch.