Completed
Branch BUG-11108-ticket-reserved-coun... (144d27)
by
unknown
14:21 queued 17s
created

EE_Base_Class   D

Complexity

Total Complexity 314

Size/Duplication

Total Lines 3129
Duplicated Lines 3.71 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 0
Metric Value
dl 116
loc 3129
rs 4.4102
c 0
b 0
f 0
wmc 314
lcom 2
cbo 8

91 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 19 72 16
A allow_persist() 0 4 1
A set_allow_persist() 0 4 1
A get_original() 0 9 3
A get_class() 0 4 1
D set() 0 83 20
A setCustomSelectsValues() 0 4 1
A getCustomSelect() 0 6 2
A set_field_or_extra_meta() 0 10 2
A get_field_or_extra_meta() 0 10 2
B set_timezone() 0 16 5
A get_timezone() 0 4 1
A set_date_format() 0 6 1
A set_time_format() 0 6 1
A get_format() 0 4 2
B cache() 0 57 8
A _set_cached_property() 0 7 2
A _get_cached_property() 0 14 4
A _get_fresh_property() 0 15 4
A _prepare_datetime_field() 0 20 3
A _clear_cached_properties() 0 4 1
A _clear_cached_property() 0 6 2
A ensure_related_thing_is_model_obj() 0 8 1
C clear_cache() 0 73 17
B update_cache_after_object_save() 0 30 5
A get_one_from_cache() 0 10 3
C get_all_from_cache() 0 32 7
B next_x() 12 12 6
B previous_x() 16 16 6
B next() 12 12 6
B previous() 12 12 6
B set_from_db() 0 22 4
A get() 0 4 1
A get_raw() 0 7 3
A get_DateTime_object() 15 20 2
A e() 0 4 1
A f() 0 4 1
A get_f() 0 4 1
A get_pretty() 0 4 1
A _get_datetime() 0 13 4
A get_date() 0 4 1
A e_date() 0 4 1
A get_time() 0 4 1
A e_time() 0 4 1
A get_datetime() 0 4 1
A e_datetime() 0 4 1
A get_i18n_datetime() 0 11 2
A _get_dtt_field_settings() 0 18 2
A _set_time_for() 0 4 1
A _set_date_for() 0 4 1
B _set_date_time() 0 25 4
B display_in_my_timezone() 0 31 3
B delete() 0 28 1
A _delete() 0 4 1
A delete_permanently() 0 20 1
B refresh_cache_of_related_objects() 0 23 6
D save() 0 120 16
B _update_cached_related_model_objs_fks() 0 17 5
B save_new_cached_related_model_objs() 0 36 6
A get_model() 0 10 2
A _get_object_from_entity_mapper() 0 13 3
D _check_for_object() 0 37 9
A _get_model() 0 17 2
A _get_model_instance_with_name() 0 7 1
A _get_model_classname() 0 9 2
A _get_primary_key_name() 0 12 2
A ID() 0 9 2
B _add_relation_to() 12 54 6
B _remove_relation_to() 0 29 3
B _remove_relations() 0 32 4
B get_many_related() 0 35 5
A count_related() 0 10 1
A sum_related() 0 9 1
C get_first_related() 0 50 7
A delete_related() 9 14 2
A delete_related_permanently() 9 14 2
A is_set() 0 4 1
A _property_exists() 0 18 3
A model_field_array() 0 10 2
A __call() 0 19 2
A update_extra_meta() 0 21 4
B add_extra_meta() 0 27 3
A delete_extra_meta() 0 14 2
B get_extra_meta() 0 34 6
C all_extra_meta_array() 0 26 7
A name() 0 15 3
A in_entity_map() 0 5 2
B refresh_from_db() 0 22 4
A __toString() 0 9 2
B __sleep() 0 23 5
A __wakeup() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EE_Base_Class often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EE_Base_Class, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use EventEspresso\core\exceptions\InvalidDataTypeException;
4
use EventEspresso\core\exceptions\InvalidInterfaceException;
5
6
defined('EVENT_ESPRESSO_VERSION') || exit('NO direct script access allowed');
7
8
/**
9
 * EE_Base_Class class
10
 *
11
 * @package     Event Espresso
12
 * @subpackage  includes/classes/EE_Base_Class.class.php
13
 * @author      Michael Nelson
14
 */
15
abstract class EE_Base_Class
16
{
17
18
    /**
19
     * This is an array of the original properties and values provided during construction
20
     * of this model object. (keys are model field names, values are their values).
21
     * This list is important to remember so that when we are merging data from the db, we know
22
     * which values to override and which to not override.
23
     *
24
     * @var array
25
     */
26
    protected $_props_n_values_provided_in_constructor;
27
28
    /**
29
     * Timezone
30
     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
31
     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
32
     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
33
     * access to it.
34
     *
35
     * @var string
36
     */
37
    protected $_timezone;
38
39
    /**
40
     * date format
41
     * pattern or format for displaying dates
42
     *
43
     * @var string $_dt_frmt
44
     */
45
    protected $_dt_frmt;
46
47
    /**
48
     * time format
49
     * pattern or format for displaying time
50
     *
51
     * @var string $_tm_frmt
52
     */
53
    protected $_tm_frmt;
54
55
    /**
56
     * This property is for holding a cached array of object properties indexed by property name as the key.
57
     * The purpose of this is for setting a cache on properties that may have calculated values after a
58
     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
59
     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
60
     *
61
     * @var array
62
     */
63
    protected $_cached_properties = array();
64
65
    /**
66
     * An array containing keys of the related model, and values are either an array of related mode objects or a
67
     * single
68
     * related model object. see the model's _model_relations. The keys should match those specified. And if the
69
     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
70
     * all others have an array)
71
     *
72
     * @var array
73
     */
74
    protected $_model_relations = array();
75
76
    /**
77
     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
78
     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
79
     *
80
     * @var array
81
     */
82
    protected $_fields = array();
83
84
    /**
85
     * @var boolean indicating whether or not this model object is intended to ever be saved
86
     * For example, we might create model objects intended to only be used for the duration
87
     * of this request and to be thrown away, and if they were accidentally saved
88
     * it would be a bug.
89
     */
90
    protected $_allow_persist = true;
91
92
    /**
93
     * @var boolean indicating whether or not this model object's properties have changed since construction
94
     */
95
    protected $_has_changes = false;
96
97
    /**
98
     * @var EEM_Base
99
     */
100
    protected $_model;
101
102
    /**
103
     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
104
     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
105
     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
106
     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
107
     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
108
     * array as:
109
     * array(
110
     *  'Registration_Count' => 24
111
     * );
112
     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
113
     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
114
     * info)
115
     *
116
     * @var array
117
     */
118
    protected $custom_selection_results = array();
119
120
121
    /**
122
     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
123
     * play nice
124
     *
125
     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
126
     *                                                         layer of the model's _fields array, (eg, EVT_ID,
127
     *                                                         TXN_amount, QST_name, etc) and values are their values
128
     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
129
     *                                                         corresponding db model or not.
130
     * @param string  $timezone                                indicate what timezone you want any datetime fields to
131
     *                                                         be in when instantiating a EE_Base_Class object.
132
     * @param array   $date_formats                            An array of date formats to set on construct where first
133
     *                                                         value is the date_format and second value is the time
134
     *                                                         format.
135
     * @throws InvalidArgumentException
136
     * @throws InvalidInterfaceException
137
     * @throws InvalidDataTypeException
138
     * @throws EE_Error
139
     * @throws ReflectionException
140
     */
141
    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
142
    {
143
        $className = get_class($this);
144
        do_action("AHEE__{$className}__construct", $this, $fieldValues);
145
        $model        = $this->get_model();
146
        $model_fields = $model->field_settings(false);
147
        // ensure $fieldValues is an array
148
        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
149
        // verify client code has not passed any invalid field names
150
        foreach ($fieldValues as $field_name => $field_value) {
151 View Code Duplication
            if (! isset($model_fields[ $field_name ])) {
152
                throw new EE_Error(
153
                    sprintf(
154
                        esc_html__(
155
                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
156
                            'event_espresso'
157
                        ),
158
                        $field_name,
159
                        get_class($this),
160
                        implode(', ', array_keys($model_fields))
161
                    )
162
                );
163
            }
164
        }
165
        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
166
        if (! empty($date_formats) && is_array($date_formats)) {
167
            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
168
        } else {
169
            //set default formats for date and time
170
            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
171
            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
172
        }
173
        //if db model is instantiating
174
        if ($bydb) {
175
            //client code has indicated these field values are from the database
176 View Code Duplication
            foreach ($model_fields as $fieldName => $field) {
177
                $this->set_from_db(
178
                    $fieldName,
179
                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
180
                );
181
            }
182
        } else {
183
            //we're constructing a brand
184
            //new instance of the model object. Generally, this means we'll need to do more field validation
185 View Code Duplication
            foreach ($model_fields as $fieldName => $field) {
186
                $this->set(
187
                    $fieldName,
188
                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null, true
189
                );
190
            }
191
        }
192
        //remember what values were passed to this constructor
193
        $this->_props_n_values_provided_in_constructor = $fieldValues;
194
        //remember in entity mapper
195
        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
196
            $model->add_to_entity_map($this);
197
        }
198
        //setup all the relations
199
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
200
            if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
                $this->_model_relations[ $relation_name ] = null;
202
            } else {
203
                $this->_model_relations[ $relation_name ] = array();
204
            }
205
        }
206
        /**
207
         * Action done at the end of each model object construction
208
         *
209
         * @param EE_Base_Class $this the model object just created
210
         */
211
        do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
    }
213
214
215
    /**
216
     * Gets whether or not this model object is allowed to persist/be saved to the database.
217
     *
218
     * @return boolean
219
     */
220
    public function allow_persist()
221
    {
222
        return $this->_allow_persist;
223
    }
224
225
226
    /**
227
     * Sets whether or not this model object should be allowed to be saved to the DB.
228
     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
229
     * you got new information that somehow made you change your mind.
230
     *
231
     * @param boolean $allow_persist
232
     * @return boolean
233
     */
234
    public function set_allow_persist($allow_persist)
235
    {
236
        return $this->_allow_persist = $allow_persist;
237
    }
238
239
240
    /**
241
     * Gets the field's original value when this object was constructed during this request.
242
     * This can be helpful when determining if a model object has changed or not
243
     *
244
     * @param string $field_name
245
     * @return mixed|null
246
     * @throws ReflectionException
247
     * @throws InvalidArgumentException
248
     * @throws InvalidInterfaceException
249
     * @throws InvalidDataTypeException
250
     * @throws EE_Error
251
     */
252
    public function get_original($field_name)
253
    {
254
        if (isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
            && $field_settings = $this->get_model()->field_settings_for($field_name)
256
        ) {
257
            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
        }
259
        return null;
260
    }
261
262
263
    /**
264
     * @param EE_Base_Class $obj
265
     * @return string
266
     */
267
    public function get_class($obj)
268
    {
269
        return get_class($obj);
270
    }
271
272
273
    /**
274
     * Overrides parent because parent expects old models.
275
     * This also doesn't do any validation, and won't work for serialized arrays
276
     *
277
     * @param    string $field_name
278
     * @param    mixed  $field_value
279
     * @param bool      $use_default
280
     * @throws InvalidArgumentException
281
     * @throws InvalidInterfaceException
282
     * @throws InvalidDataTypeException
283
     * @throws EE_Error
284
     * @throws ReflectionException
285
     * @throws ReflectionException
286
     * @throws ReflectionException
287
     */
288
    public function set($field_name, $field_value, $use_default = false)
289
    {
290
        // if not using default and nothing has changed, and object has already been setup (has ID),
291
        // then don't do anything
292
        if (
293
            ! $use_default
294
            && $this->_fields[ $field_name ] === $field_value
295
            && $this->ID()
296
        ) {
297
            return;
298
        }
299
        $model              = $this->get_model();
300
        $this->_has_changes = true;
301
        $field_obj          = $model->field_settings_for($field_name);
302
        if ($field_obj instanceof EE_Model_Field_Base) {
303
            //			if ( method_exists( $field_obj, 'set_timezone' )) {
304
            if ($field_obj instanceof EE_Datetime_Field) {
305
                $field_obj->set_timezone($this->_timezone);
306
                $field_obj->set_date_format($this->_dt_frmt);
307
                $field_obj->set_time_format($this->_tm_frmt);
308
            }
309
            $holder_of_value = $field_obj->prepare_for_set($field_value);
310
            //should the value be null?
311
            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
                $this->_fields[ $field_name ] = $field_obj->get_default_value();
313
                /**
314
                 * To save having to refactor all the models, if a default value is used for a
315
                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
                 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
                 * object.
318
                 *
319
                 * @since 4.6.10+
320
                 */
321
                if (
322
                    $field_obj instanceof EE_Datetime_Field
323
                    && $this->_fields[ $field_name ] !== null
324
                    && ! $this->_fields[ $field_name ] instanceof DateTime
325
                ) {
326
                    empty($this->_fields[ $field_name ])
327
                        ? $this->set($field_name, time())
328
                        : $this->set($field_name, $this->_fields[ $field_name ]);
329
                }
330
            } else {
331
                $this->_fields[ $field_name ] = $holder_of_value;
332
            }
333
            //if we're not in the constructor...
334
            //now check if what we set was a primary key
335
            if (
336
                //note: props_n_values_provided_in_constructor is only set at the END of the constructor
337
                $this->_props_n_values_provided_in_constructor
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_props_n_values_provided_in_constructor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
338
                && $field_value
339
                && $field_name === $model->primary_key_name()
340
            ) {
341
                //if so, we want all this object's fields to be filled either with
342
                //what we've explicitly set on this model
343
                //or what we have in the db
344
                // echo "setting primary key!";
345
                $fields_on_model = self::_get_model(get_class($this))->field_settings();
346
                $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
                foreach ($fields_on_model as $field_obj) {
348
                    if (! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
349
                        && $field_obj->get_name() !== $field_name
350
                    ) {
351
                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
352
                    }
353
                }
354
                //oh this model object has an ID? well make sure its in the entity mapper
355
                $model->add_to_entity_map($this);
356
            }
357
            //let's unset any cache for this field_name from the $_cached_properties property.
358
            $this->_clear_cached_property($field_name);
359
        } else {
360
            throw new EE_Error(
361
                sprintf(
362
                    esc_html__(
363
                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
364
                        'event_espresso'
365
                    ),
366
                    $field_name
367
                )
368
            );
369
        }
370
    }
371
372
373
    /**
374
     * Set custom select values for model.
375
     *
376
     * @param array $custom_select_values
377
     */
378
    public function setCustomSelectsValues(array $custom_select_values)
379
    {
380
        $this->custom_selection_results = $custom_select_values;
381
    }
382
383
384
    /**
385
     * Returns the custom select value for the provided alias if its set.
386
     * If not set, returns null.
387
     *
388
     * @param string $alias
389
     * @return string|int|float|null
390
     */
391
    public function getCustomSelect($alias)
392
    {
393
        return isset($this->custom_selection_results[ $alias ])
394
            ? $this->custom_selection_results[ $alias ]
395
            : null;
396
    }
397
398
399
    /**
400
     * This sets the field value on the db column if it exists for the given $column_name or
401
     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
402
     *
403
     * @see EE_message::get_column_value for related documentation on the necessity of this method.
404
     * @param string $field_name  Must be the exact column name.
405
     * @param mixed  $field_value The value to set.
406
     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
407
     * @throws InvalidArgumentException
408
     * @throws InvalidInterfaceException
409
     * @throws InvalidDataTypeException
410
     * @throws EE_Error
411
     * @throws ReflectionException
412
     */
413
    public function set_field_or_extra_meta($field_name, $field_value)
414
    {
415
        if ($this->get_model()->has_field($field_name)) {
416
            $this->set($field_name, $field_value);
417
            return true;
418
        }
419
        //ensure this object is saved first so that extra meta can be properly related.
420
        $this->save();
421
        return $this->update_extra_meta($field_name, $field_value);
422
    }
423
424
425
    /**
426
     * This retrieves the value of the db column set on this class or if that's not present
427
     * it will attempt to retrieve from extra_meta if found.
428
     * Example Usage:
429
     * Via EE_Message child class:
430
     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
431
     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
432
     * also have additional main fields specific to the messenger.  The system accommodates those extra
433
     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
434
     * value for those extra fields dynamically via the EE_message object.
435
     *
436
     * @param  string $field_name expecting the fully qualified field name.
437
     * @return mixed|null  value for the field if found.  null if not found.
438
     * @throws ReflectionException
439
     * @throws InvalidArgumentException
440
     * @throws InvalidInterfaceException
441
     * @throws InvalidDataTypeException
442
     * @throws EE_Error
443
     */
444
    public function get_field_or_extra_meta($field_name)
445
    {
446
        if ($this->get_model()->has_field($field_name)) {
447
            $column_value = $this->get($field_name);
448
        } else {
449
            //This isn't a column in the main table, let's see if it is in the extra meta.
450
            $column_value = $this->get_extra_meta($field_name, true, null);
451
        }
452
        return $column_value;
453
    }
454
455
456
    /**
457
     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
458
     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
459
     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
460
     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
461
     *
462
     * @access public
463
     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
464
     * @return void
465
     * @throws InvalidArgumentException
466
     * @throws InvalidInterfaceException
467
     * @throws InvalidDataTypeException
468
     * @throws EE_Error
469
     * @throws ReflectionException
470
     */
471
    public function set_timezone($timezone = '')
472
    {
473
        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
474
        //make sure we clear all cached properties because they won't be relevant now
475
        $this->_clear_cached_properties();
476
        //make sure we update field settings and the date for all EE_Datetime_Fields
477
        $model_fields = $this->get_model()->field_settings(false);
478
        foreach ($model_fields as $field_name => $field_obj) {
479
            if ($field_obj instanceof EE_Datetime_Field) {
480
                $field_obj->set_timezone($this->_timezone);
481
                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
482
                    $this->_fields[ $field_name ]->setTimezone(new DateTimeZone($this->_timezone));
483
                }
484
            }
485
        }
486
    }
487
488
489
    /**
490
     * This just returns whatever is set for the current timezone.
491
     *
492
     * @access public
493
     * @return string timezone string
494
     */
495
    public function get_timezone()
496
    {
497
        return $this->_timezone;
498
    }
499
500
501
    /**
502
     * This sets the internal date format to what is sent in to be used as the new default for the class
503
     * internally instead of wp set date format options
504
     *
505
     * @since 4.6
506
     * @param string $format should be a format recognizable by PHP date() functions.
507
     */
508
    public function set_date_format($format)
509
    {
510
        $this->_dt_frmt = $format;
511
        //clear cached_properties because they won't be relevant now.
512
        $this->_clear_cached_properties();
513
    }
514
515
516
    /**
517
     * This sets the internal time format string to what is sent in to be used as the new default for the
518
     * class internally instead of wp set time format options.
519
     *
520
     * @since 4.6
521
     * @param string $format should be a format recognizable by PHP date() functions.
522
     */
523
    public function set_time_format($format)
524
    {
525
        $this->_tm_frmt = $format;
526
        //clear cached_properties because they won't be relevant now.
527
        $this->_clear_cached_properties();
528
    }
529
530
531
    /**
532
     * This returns the current internal set format for the date and time formats.
533
     *
534
     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
535
     *                             where the first value is the date format and the second value is the time format.
536
     * @return mixed string|array
537
     */
538
    public function get_format($full = true)
539
    {
540
        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
541
    }
542
543
544
    /**
545
     * cache
546
     * stores the passed model object on the current model object.
547
     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
548
     *
549
     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
550
     *                                       'Registration' associated with this model object
551
     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
552
     *                                       that could be a payment or a registration)
553
     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
554
     *                                       items which will be stored in an array on this object
555
     * @throws ReflectionException
556
     * @throws InvalidArgumentException
557
     * @throws InvalidInterfaceException
558
     * @throws InvalidDataTypeException
559
     * @throws EE_Error
560
     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
561
     *                                       related thing, no array)
562
     */
563
    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
564
    {
565
        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
566
        if (! $object_to_cache instanceof EE_Base_Class) {
567
            return false;
568
        }
569
        // also get "how" the object is related, or throw an error
570
        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
571
            throw new EE_Error(
572
                sprintf(
573
                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
574
                    $relationName,
575
                    get_class($this)
576
                )
577
            );
578
        }
579
        // how many things are related ?
580
        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
581
            // if it's a "belongs to" relationship, then there's only one related model object
582
            // eg, if this is a registration, there's only 1 attendee for it
583
            // so for these model objects just set it to be cached
584
            $this->_model_relations[ $relationName ] = $object_to_cache;
585
            $return                                  = true;
586
        } else {
587
            // otherwise, this is the "many" side of a one to many relationship,
588
            // so we'll add the object to the array of related objects for that type.
589
            // eg: if this is an event, there are many registrations for that event,
590
            // so we cache the registrations in an array
591
            if (! is_array($this->_model_relations[ $relationName ])) {
592
                // if for some reason, the cached item is a model object,
593
                // then stick that in the array, otherwise start with an empty array
594
                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
595
                                                           instanceof
596
                                                           EE_Base_Class
597
                    ? array($this->_model_relations[ $relationName ]) : array();
598
            }
599
            // first check for a cache_id which is normally empty
600
            if (! empty($cache_id)) {
601
                // if the cache_id exists, then it means we are purposely trying to cache this
602
                // with a known key that can then be used to retrieve the object later on
603
                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
604
                $return                                               = $cache_id;
605
            } elseif ($object_to_cache->ID()) {
606
                // OR the cached object originally came from the db, so let's just use it's PK for an ID
607
                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
608
                $return                                                            = $object_to_cache->ID();
609
            } else {
610
                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
611
                $this->_model_relations[ $relationName ][] = $object_to_cache;
612
                // move the internal pointer to the end of the array
613
                end($this->_model_relations[ $relationName ]);
614
                // and grab the key so that we can return it
615
                $return = key($this->_model_relations[ $relationName ]);
616
            }
617
        }
618
        return $return;
619
    }
620
621
622
    /**
623
     * For adding an item to the cached_properties property.
624
     *
625
     * @access protected
626
     * @param string      $fieldname the property item the corresponding value is for.
627
     * @param mixed       $value     The value we are caching.
628
     * @param string|null $cache_type
629
     * @return void
630
     * @throws ReflectionException
631
     * @throws InvalidArgumentException
632
     * @throws InvalidInterfaceException
633
     * @throws InvalidDataTypeException
634
     * @throws EE_Error
635
     */
636
    protected function _set_cached_property($fieldname, $value, $cache_type = null)
637
    {
638
        //first make sure this property exists
639
        $this->get_model()->field_settings_for($fieldname);
640
        $cache_type                                            = empty($cache_type) ? 'standard' : $cache_type;
641
        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
642
    }
643
644
645
    /**
646
     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
647
     * This also SETS the cache if we return the actual property!
648
     *
649
     * @param string $fieldname        the name of the property we're trying to retrieve
650
     * @param bool   $pretty
651
     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
652
     *                                 (in cases where the same property may be used for different outputs
653
     *                                 - i.e. datetime, money etc.)
654
     *                                 It can also accept certain pre-defined "schema" strings
655
     *                                 to define how to output the property.
656
     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
657
     * @return mixed                   whatever the value for the property is we're retrieving
658
     * @throws ReflectionException
659
     * @throws InvalidArgumentException
660
     * @throws InvalidInterfaceException
661
     * @throws InvalidDataTypeException
662
     * @throws EE_Error
663
     */
664
    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
665
    {
666
        //verify the field exists
667
        $model = $this->get_model();
668
        $model->field_settings_for($fieldname);
669
        $cache_type = $pretty ? 'pretty' : 'standard';
670
        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
671
        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
672
            return $this->_cached_properties[ $fieldname ][ $cache_type ];
673
        }
674
        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
675
        $this->_set_cached_property($fieldname, $value, $cache_type);
676
        return $value;
677
    }
678
679
680
    /**
681
     * If the cache didn't fetch the needed item, this fetches it.
682
     *
683
     * @param string $fieldname
684
     * @param bool   $pretty
685
     * @param string $extra_cache_ref
686
     * @return mixed
687
     * @throws InvalidArgumentException
688
     * @throws InvalidInterfaceException
689
     * @throws InvalidDataTypeException
690
     * @throws EE_Error
691
     * @throws ReflectionException
692
     */
693
    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
694
    {
695
        $field_obj = $this->get_model()->field_settings_for($fieldname);
696
        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
697
        if ($field_obj instanceof EE_Datetime_Field) {
698
            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
699
        }
700
        if (! isset($this->_fields[ $fieldname ])) {
701
            $this->_fields[ $fieldname ] = null;
702
        }
703
        $value = $pretty
704
            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
705
            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
706
        return $value;
707
    }
708
709
710
    /**
711
     * set timezone, formats, and output for EE_Datetime_Field objects
712
     *
713
     * @param \EE_Datetime_Field $datetime_field
714
     * @param bool               $pretty
715
     * @param null               $date_or_time
716
     * @return void
717
     * @throws InvalidArgumentException
718
     * @throws InvalidInterfaceException
719
     * @throws InvalidDataTypeException
720
     * @throws EE_Error
721
     */
722
    protected function _prepare_datetime_field(
723
        EE_Datetime_Field $datetime_field,
724
        $pretty = false,
725
        $date_or_time = null
726
    ) {
727
        $datetime_field->set_timezone($this->_timezone);
728
        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
729
        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
730
        //set the output returned
731
        switch ($date_or_time) {
732
            case 'D' :
733
                $datetime_field->set_date_time_output('date');
734
                break;
735
            case 'T' :
736
                $datetime_field->set_date_time_output('time');
737
                break;
738
            default :
739
                $datetime_field->set_date_time_output();
740
        }
741
    }
742
743
744
    /**
745
     * This just takes care of clearing out the cached_properties
746
     *
747
     * @return void
748
     */
749
    protected function _clear_cached_properties()
750
    {
751
        $this->_cached_properties = array();
752
    }
753
754
755
    /**
756
     * This just clears out ONE property if it exists in the cache
757
     *
758
     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
759
     * @return void
760
     */
761
    protected function _clear_cached_property($property_name)
762
    {
763
        if (isset($this->_cached_properties[ $property_name ])) {
764
            unset($this->_cached_properties[ $property_name ]);
765
        }
766
    }
767
768
769
    /**
770
     * Ensures that this related thing is a model object.
771
     *
772
     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
773
     * @param string $model_name   name of the related thing, eg 'Attendee',
774
     * @return EE_Base_Class
775
     * @throws ReflectionException
776
     * @throws InvalidArgumentException
777
     * @throws InvalidInterfaceException
778
     * @throws InvalidDataTypeException
779
     * @throws EE_Error
780
     */
781
    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
782
    {
783
        $other_model_instance = self::_get_model_instance_with_name(
784
            self::_get_model_classname($model_name),
785
            $this->_timezone
786
        );
787
        return $other_model_instance->ensure_is_obj($object_or_id);
788
    }
789
790
791
    /**
792
     * Forgets the cached model of the given relation Name. So the next time we request it,
793
     * we will fetch it again from the database. (Handy if you know it's changed somehow).
794
     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
795
     * then only remove that one object from our cached array. Otherwise, clear the entire list
796
     *
797
     * @param string $relationName                         one of the keys in the _model_relations array on the model.
798
     *                                                     Eg 'Registration'
799
     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
800
     *                                                     if you intend to use $clear_all = TRUE, or the relation only
801
     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
802
     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
803
     *                                                     this is HasMany or HABTM.
804
     * @throws ReflectionException
805
     * @throws InvalidArgumentException
806
     * @throws InvalidInterfaceException
807
     * @throws InvalidDataTypeException
808
     * @throws EE_Error
809
     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
810
     *                                                     relation from all
811
     */
812
    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
813
    {
814
        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
815
        $index_in_cache        = '';
816
        if (! $relationship_to_model) {
817
            throw new EE_Error(
818
                sprintf(
819
                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
820
                    $relationName,
821
                    get_class($this)
822
                )
823
            );
824
        }
825
        if ($clear_all) {
826
            $obj_removed                             = true;
827
            $this->_model_relations[ $relationName ] = null;
828
        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
829
            $obj_removed                             = $this->_model_relations[ $relationName ];
830
            $this->_model_relations[ $relationName ] = null;
831
        } else {
832
            if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
833
                && $object_to_remove_or_index_into_array->ID()
834
            ) {
835
                $index_in_cache = $object_to_remove_or_index_into_array->ID();
836
                if (is_array($this->_model_relations[ $relationName ])
837
                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
838
                ) {
839
                    $index_found_at = null;
840
                    //find this object in the array even though it has a different key
841
                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
842
                        /** @noinspection TypeUnsafeComparisonInspection */
843
                        if (
844
                            $obj instanceof EE_Base_Class
845
                            && (
846
                                $obj == $object_to_remove_or_index_into_array
847
                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
848
                            )
849
                        ) {
850
                            $index_found_at = $index;
851
                            break;
852
                        }
853
                    }
854
                    if ($index_found_at) {
855
                        $index_in_cache = $index_found_at;
856
                    } else {
857
                        //it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
858
                        //if it wasn't in it to begin with. So we're done
859
                        return $object_to_remove_or_index_into_array;
860
                    }
861
                }
862
            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
863
                //so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
864
                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
865
                    /** @noinspection TypeUnsafeComparisonInspection */
866
                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
867
                        $index_in_cache = $index;
868
                    }
869
                }
870
            } else {
871
                $index_in_cache = $object_to_remove_or_index_into_array;
872
            }
873
            //supposedly we've found it. But it could just be that the client code
874
            //provided a bad index/object
875
            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
876
                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
877
                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
878
            } else {
879
                //that thing was never cached anyways.
880
                $obj_removed = null;
881
            }
882
        }
883
        return $obj_removed;
884
    }
885
886
887
    /**
888
     * update_cache_after_object_save
889
     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
890
     * obtained after being saved to the db
891
     *
892
     * @param string        $relationName       - the type of object that is cached
893
     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
894
     * @param string        $current_cache_id   - the ID that was used when originally caching the object
895
     * @return boolean TRUE on success, FALSE on fail
896
     * @throws ReflectionException
897
     * @throws InvalidArgumentException
898
     * @throws InvalidInterfaceException
899
     * @throws InvalidDataTypeException
900
     * @throws EE_Error
901
     */
902
    public function update_cache_after_object_save(
903
        $relationName,
904
        EE_Base_Class $newly_saved_object,
905
        $current_cache_id = ''
906
    ) {
907
        // verify that incoming object is of the correct type
908
        $obj_class = 'EE_' . $relationName;
909
        if ($newly_saved_object instanceof $obj_class) {
910
            /* @type EE_Base_Class $newly_saved_object */
911
            // now get the type of relation
912
            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
913
            // if this is a 1:1 relationship
914
            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
915
                // then just replace the cached object with the newly saved object
916
                $this->_model_relations[ $relationName ] = $newly_saved_object;
917
                return true;
918
                // or if it's some kind of sordid feral polyamorous relationship...
919
            }
920
            if (is_array($this->_model_relations[ $relationName ])
921
                      && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
922
            ) {
923
                // then remove the current cached item
924
                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
925
                // and cache the newly saved object using it's new ID
926
                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
927
                return true;
928
            }
929
        }
930
        return false;
931
    }
932
933
934
    /**
935
     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
936
     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
937
     *
938
     * @param string $relationName
939
     * @return EE_Base_Class
940
     */
941
    public function get_one_from_cache($relationName)
942
    {
943
        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
944
            ? $this->_model_relations[ $relationName ]
945
            : null;
946
        if (is_array($cached_array_or_object)) {
947
            return array_shift($cached_array_or_object);
948
        }
949
        return $cached_array_or_object;
950
    }
951
952
953
    /**
954
     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
955
     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
956
     *
957
     * @param string $relationName
958
     * @throws ReflectionException
959
     * @throws InvalidArgumentException
960
     * @throws InvalidInterfaceException
961
     * @throws InvalidDataTypeException
962
     * @throws EE_Error
963
     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
964
     */
965
    public function get_all_from_cache($relationName)
966
    {
967
        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
968
        // if the result is not an array, but exists, make it an array
969
        $objects = is_array($objects) ? $objects : array($objects);
970
        //bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
971
        //basically, if this model object was stored in the session, and these cached model objects
972
        //already have IDs, let's make sure they're in their model's entity mapper
973
        //otherwise we will have duplicates next time we call
974
        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
975
        $model = EE_Registry::instance()->load_model($relationName);
976
        foreach ($objects as $model_object) {
977
            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
978
                //ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
979
                if ($model_object->ID()) {
980
                    $model->add_to_entity_map($model_object);
981
                }
982
            } else {
983
                throw new EE_Error(
984
                    sprintf(
985
                        esc_html__(
986
                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
987
                            'event_espresso'
988
                        ),
989
                        $relationName,
990
                        gettype($model_object)
991
                    )
992
                );
993
            }
994
        }
995
        return $objects;
996
    }
997
998
999
    /**
1000
     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1001
     * matching the given query conditions.
1002
     *
1003
     * @param null  $field_to_order_by  What field is being used as the reference point.
1004
     * @param int   $limit              How many objects to return.
1005
     * @param array $query_params       Any additional conditions on the query.
1006
     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1007
     *                                  you can indicate just the columns you want returned
1008
     * @return array|EE_Base_Class[]
1009
     * @throws ReflectionException
1010
     * @throws InvalidArgumentException
1011
     * @throws InvalidInterfaceException
1012
     * @throws InvalidDataTypeException
1013
     * @throws EE_Error
1014
     */
1015 View Code Duplication
    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1016
    {
1017
        $model         = $this->get_model();
1018
        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1019
            ? $model->get_primary_key_field()->get_name()
1020
            : $field_to_order_by;
1021
        $current_value = ! empty($field) ? $this->get($field) : null;
1022
        if (empty($field) || empty($current_value)) {
1023
            return array();
1024
        }
1025
        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1026
    }
1027
1028
1029
    /**
1030
     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1031
     * matching the given query conditions.
1032
     *
1033
     * @param null  $field_to_order_by  What field is being used as the reference point.
1034
     * @param int   $limit              How many objects to return.
1035
     * @param array $query_params       Any additional conditions on the query.
1036
     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1037
     *                                  you can indicate just the columns you want returned
1038
     * @return array|EE_Base_Class[]
1039
     * @throws ReflectionException
1040
     * @throws InvalidArgumentException
1041
     * @throws InvalidInterfaceException
1042
     * @throws InvalidDataTypeException
1043
     * @throws EE_Error
1044
     */
1045 View Code Duplication
    public function previous_x(
1046
        $field_to_order_by = null,
1047
        $limit = 1,
1048
        $query_params = array(),
1049
        $columns_to_select = null
1050
    ) {
1051
        $model         = $this->get_model();
1052
        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1053
            ? $model->get_primary_key_field()->get_name()
1054
            : $field_to_order_by;
1055
        $current_value = ! empty($field) ? $this->get($field) : null;
1056
        if (empty($field) || empty($current_value)) {
1057
            return array();
1058
        }
1059
        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1060
    }
1061
1062
1063
    /**
1064
     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1065
     * matching the given query conditions.
1066
     *
1067
     * @param null  $field_to_order_by  What field is being used as the reference point.
1068
     * @param array $query_params       Any additional conditions on the query.
1069
     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1070
     *                                  you can indicate just the columns you want returned
1071
     * @return array|EE_Base_Class
1072
     * @throws ReflectionException
1073
     * @throws InvalidArgumentException
1074
     * @throws InvalidInterfaceException
1075
     * @throws InvalidDataTypeException
1076
     * @throws EE_Error
1077
     */
1078 View Code Duplication
    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1079
    {
1080
        $model         = $this->get_model();
1081
        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1082
            ? $model->get_primary_key_field()->get_name()
1083
            : $field_to_order_by;
1084
        $current_value = ! empty($field) ? $this->get($field) : null;
1085
        if (empty($field) || empty($current_value)) {
1086
            return array();
1087
        }
1088
        return $model->next($current_value, $field, $query_params, $columns_to_select);
1089
    }
1090
1091
1092
    /**
1093
     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1094
     * matching the given query conditions.
1095
     *
1096
     * @param null  $field_to_order_by  What field is being used as the reference point.
1097
     * @param array $query_params       Any additional conditions on the query.
1098
     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1099
     *                                  you can indicate just the column you want returned
1100
     * @return array|EE_Base_Class
1101
     * @throws ReflectionException
1102
     * @throws InvalidArgumentException
1103
     * @throws InvalidInterfaceException
1104
     * @throws InvalidDataTypeException
1105
     * @throws EE_Error
1106
     */
1107 View Code Duplication
    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1108
    {
1109
        $model         = $this->get_model();
1110
        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1111
            ? $model->get_primary_key_field()->get_name()
1112
            : $field_to_order_by;
1113
        $current_value = ! empty($field) ? $this->get($field) : null;
1114
        if (empty($field) || empty($current_value)) {
1115
            return array();
1116
        }
1117
        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1118
    }
1119
1120
1121
    /**
1122
     * Overrides parent because parent expects old models.
1123
     * This also doesn't do any validation, and won't work for serialized arrays
1124
     *
1125
     * @param string $field_name
1126
     * @param mixed  $field_value_from_db
1127
     * @throws ReflectionException
1128
     * @throws InvalidArgumentException
1129
     * @throws InvalidInterfaceException
1130
     * @throws InvalidDataTypeException
1131
     * @throws EE_Error
1132
     */
1133
    public function set_from_db($field_name, $field_value_from_db)
1134
    {
1135
        $field_obj = $this->get_model()->field_settings_for($field_name);
1136
        if ($field_obj instanceof EE_Model_Field_Base) {
1137
            //you would think the DB has no NULLs for non-null label fields right? wrong!
1138
            //eg, a CPT model object could have an entry in the posts table, but no
1139
            //entry in the meta table. Meaning that all its columns in the meta table
1140
            //are null! yikes! so when we find one like that, use defaults for its meta columns
1141
            if ($field_value_from_db === null) {
1142
                if ($field_obj->is_nullable()) {
1143
                    //if the field allows nulls, then let it be null
1144
                    $field_value = null;
1145
                } else {
1146
                    $field_value = $field_obj->get_default_value();
1147
                }
1148
            } else {
1149
                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1150
            }
1151
            $this->_fields[ $field_name ] = $field_value;
1152
            $this->_clear_cached_property($field_name);
1153
        }
1154
    }
1155
1156
1157
    /**
1158
     * verifies that the specified field is of the correct type
1159
     *
1160
     * @param string $field_name
1161
     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1162
     *                                (in cases where the same property may be used for different outputs
1163
     *                                - i.e. datetime, money etc.)
1164
     * @return mixed
1165
     * @throws ReflectionException
1166
     * @throws InvalidArgumentException
1167
     * @throws InvalidInterfaceException
1168
     * @throws InvalidDataTypeException
1169
     * @throws EE_Error
1170
     */
1171
    public function get($field_name, $extra_cache_ref = null)
1172
    {
1173
        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1174
    }
1175
1176
1177
    /**
1178
     * This method simply returns the RAW unprocessed value for the given property in this class
1179
     *
1180
     * @param  string $field_name A valid fieldname
1181
     * @return mixed              Whatever the raw value stored on the property is.
1182
     * @throws ReflectionException
1183
     * @throws InvalidArgumentException
1184
     * @throws InvalidInterfaceException
1185
     * @throws InvalidDataTypeException
1186
     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1187
     */
1188
    public function get_raw($field_name)
1189
    {
1190
        $field_settings = $this->get_model()->field_settings_for($field_name);
1191
        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1192
            ? $this->_fields[ $field_name ]->format('U')
1193
            : $this->_fields[ $field_name ];
1194
    }
1195
1196
1197
    /**
1198
     * This is used to return the internal DateTime object used for a field that is a
1199
     * EE_Datetime_Field.
1200
     *
1201
     * @param string $field_name               The field name retrieving the DateTime object.
1202
     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1203
     * @throws ReflectionException
1204
     * @throws InvalidArgumentException
1205
     * @throws InvalidInterfaceException
1206
     * @throws InvalidDataTypeException
1207
     * @throws EE_Error
1208
     *                                         an error is set and false returned.  If the field IS an
1209
     *                                         EE_Datetime_Field and but the field value is null, then
1210
     *                                         just null is returned (because that indicates that likely
1211
     *                                         this field is nullable).
1212
     */
1213
    public function get_DateTime_object($field_name)
1214
    {
1215
        $field_settings = $this->get_model()->field_settings_for($field_name);
1216 View Code Duplication
        if (! $field_settings instanceof EE_Datetime_Field) {
1217
            EE_Error::add_error(
1218
                sprintf(
1219
                    esc_html__(
1220
                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1221
                        'event_espresso'
1222
                    ),
1223
                    $field_name
1224
                ),
1225
                __FILE__,
1226
                __FUNCTION__,
1227
                __LINE__
1228
            );
1229
            return false;
1230
        }
1231
        return $this->_fields[ $field_name ];
1232
    }
1233
1234
1235
    /**
1236
     * To be used in template to immediately echo out the value, and format it for output.
1237
     * Eg, should call stripslashes and whatnot before echoing
1238
     *
1239
     * @param string $field_name      the name of the field as it appears in the DB
1240
     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1241
     *                                (in cases where the same property may be used for different outputs
1242
     *                                - i.e. datetime, money etc.)
1243
     * @return void
1244
     * @throws ReflectionException
1245
     * @throws InvalidArgumentException
1246
     * @throws InvalidInterfaceException
1247
     * @throws InvalidDataTypeException
1248
     * @throws EE_Error
1249
     */
1250
    public function e($field_name, $extra_cache_ref = null)
1251
    {
1252
        echo $this->get_pretty($field_name, $extra_cache_ref);
1253
    }
1254
1255
1256
    /**
1257
     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1258
     * can be easily used as the value of form input.
1259
     *
1260
     * @param string $field_name
1261
     * @return void
1262
     * @throws ReflectionException
1263
     * @throws InvalidArgumentException
1264
     * @throws InvalidInterfaceException
1265
     * @throws InvalidDataTypeException
1266
     * @throws EE_Error
1267
     */
1268
    public function f($field_name)
1269
    {
1270
        $this->e($field_name, 'form_input');
1271
    }
1272
1273
1274
    /**
1275
     * Same as `f()` but just returns the value instead of echoing it
1276
     *
1277
     * @param string $field_name
1278
     * @return string
1279
     * @throws ReflectionException
1280
     * @throws InvalidArgumentException
1281
     * @throws InvalidInterfaceException
1282
     * @throws InvalidDataTypeException
1283
     * @throws EE_Error
1284
     */
1285
    public function get_f($field_name)
1286
    {
1287
        return (string) $this->get_pretty($field_name, 'form_input');
1288
    }
1289
1290
1291
    /**
1292
     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1293
     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1294
     * to see what options are available.
1295
     *
1296
     * @param string $field_name
1297
     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1298
     *                                (in cases where the same property may be used for different outputs
1299
     *                                - i.e. datetime, money etc.)
1300
     * @return mixed
1301
     * @throws ReflectionException
1302
     * @throws InvalidArgumentException
1303
     * @throws InvalidInterfaceException
1304
     * @throws InvalidDataTypeException
1305
     * @throws EE_Error
1306
     */
1307
    public function get_pretty($field_name, $extra_cache_ref = null)
1308
    {
1309
        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1310
    }
1311
1312
1313
    /**
1314
     * This simply returns the datetime for the given field name
1315
     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1316
     * (and the equivalent e_date, e_time, e_datetime).
1317
     *
1318
     * @access   protected
1319
     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1320
     * @param string   $dt_frmt      valid datetime format used for date
1321
     *                               (if '' then we just use the default on the field,
1322
     *                               if NULL we use the last-used format)
1323
     * @param string   $tm_frmt      Same as above except this is for time format
1324
     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1325
     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1326
     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1327
     *                               if field is not a valid dtt field, or void if echoing
1328
     * @throws ReflectionException
1329
     * @throws InvalidArgumentException
1330
     * @throws InvalidInterfaceException
1331
     * @throws InvalidDataTypeException
1332
     * @throws EE_Error
1333
     */
1334
    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1335
    {
1336
        // clear cached property
1337
        $this->_clear_cached_property($field_name);
1338
        //reset format properties because they are used in get()
1339
        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1340
        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1341
        if ($echo) {
1342
            $this->e($field_name, $date_or_time);
1343
            return '';
1344
        }
1345
        return $this->get($field_name, $date_or_time);
1346
    }
1347
1348
1349
    /**
1350
     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1351
     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1352
     * other echoes the pretty value for dtt)
1353
     *
1354
     * @param  string $field_name name of model object datetime field holding the value
1355
     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1356
     * @return string            datetime value formatted
1357
     * @throws ReflectionException
1358
     * @throws InvalidArgumentException
1359
     * @throws InvalidInterfaceException
1360
     * @throws InvalidDataTypeException
1361
     * @throws EE_Error
1362
     */
1363
    public function get_date($field_name, $format = '')
1364
    {
1365
        return $this->_get_datetime($field_name, $format, null, 'D');
1366
    }
1367
1368
1369
    /**
1370
     * @param        $field_name
1371
     * @param string $format
1372
     * @throws ReflectionException
1373
     * @throws InvalidArgumentException
1374
     * @throws InvalidInterfaceException
1375
     * @throws InvalidDataTypeException
1376
     * @throws EE_Error
1377
     */
1378
    public function e_date($field_name, $format = '')
1379
    {
1380
        $this->_get_datetime($field_name, $format, null, 'D', true);
1381
    }
1382
1383
1384
    /**
1385
     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1386
     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1387
     * other echoes the pretty value for dtt)
1388
     *
1389
     * @param  string $field_name name of model object datetime field holding the value
1390
     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1391
     * @return string             datetime value formatted
1392
     * @throws ReflectionException
1393
     * @throws InvalidArgumentException
1394
     * @throws InvalidInterfaceException
1395
     * @throws InvalidDataTypeException
1396
     * @throws EE_Error
1397
     */
1398
    public function get_time($field_name, $format = '')
1399
    {
1400
        return $this->_get_datetime($field_name, null, $format, 'T');
1401
    }
1402
1403
1404
    /**
1405
     * @param        $field_name
1406
     * @param string $format
1407
     * @throws ReflectionException
1408
     * @throws InvalidArgumentException
1409
     * @throws InvalidInterfaceException
1410
     * @throws InvalidDataTypeException
1411
     * @throws EE_Error
1412
     */
1413
    public function e_time($field_name, $format = '')
1414
    {
1415
        $this->_get_datetime($field_name, null, $format, 'T', true);
1416
    }
1417
1418
1419
    /**
1420
     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1421
     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1422
     * other echoes the pretty value for dtt)
1423
     *
1424
     * @param  string $field_name name of model object datetime field holding the value
1425
     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1426
     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1427
     * @return string             datetime value formatted
1428
     * @throws ReflectionException
1429
     * @throws InvalidArgumentException
1430
     * @throws InvalidInterfaceException
1431
     * @throws InvalidDataTypeException
1432
     * @throws EE_Error
1433
     */
1434
    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1435
    {
1436
        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1437
    }
1438
1439
1440
    /**
1441
     * @param string $field_name
1442
     * @param string $dt_frmt
1443
     * @param string $tm_frmt
1444
     * @throws ReflectionException
1445
     * @throws InvalidArgumentException
1446
     * @throws InvalidInterfaceException
1447
     * @throws InvalidDataTypeException
1448
     * @throws EE_Error
1449
     */
1450
    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1451
    {
1452
        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1453
    }
1454
1455
1456
    /**
1457
     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1458
     *
1459
     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1460
     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1461
     *                           on the object will be used.
1462
     * @return string Date and time string in set locale or false if no field exists for the given
1463
     * @throws ReflectionException
1464
     * @throws InvalidArgumentException
1465
     * @throws InvalidInterfaceException
1466
     * @throws InvalidDataTypeException
1467
     * @throws EE_Error
1468
     *                           field name.
1469
     */
1470
    public function get_i18n_datetime($field_name, $format = '')
1471
    {
1472
        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1473
        return date_i18n(
1474
            $format,
1475
            EEH_DTT_Helper::get_timestamp_with_offset(
1476
                $this->get_raw($field_name),
1477
                $this->_timezone
1478
            )
1479
        );
1480
    }
1481
1482
1483
    /**
1484
     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1485
     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1486
     * thrown.
1487
     *
1488
     * @param  string $field_name The field name being checked
1489
     * @throws ReflectionException
1490
     * @throws InvalidArgumentException
1491
     * @throws InvalidInterfaceException
1492
     * @throws InvalidDataTypeException
1493
     * @throws EE_Error
1494
     * @return EE_Datetime_Field
1495
     */
1496
    protected function _get_dtt_field_settings($field_name)
1497
    {
1498
        $field = $this->get_model()->field_settings_for($field_name);
1499
        //check if field is dtt
1500
        if ($field instanceof EE_Datetime_Field) {
1501
            return $field;
1502
        }
1503
        throw new EE_Error(
1504
            sprintf(
1505
                esc_html__(
1506
                    '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',
1507
                    'event_espresso'
1508
                ),
1509
                $field_name,
1510
                self::_get_model_classname(get_class($this))
1511
            )
1512
        );
1513
    }
1514
1515
1516
1517
1518
    /**
1519
     * NOTE ABOUT BELOW:
1520
     * These convenience date and time setters are for setting date and time independently.  In other words you might
1521
     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1522
     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1523
     * method and make sure you send the entire datetime value for setting.
1524
     */
1525
    /**
1526
     * sets the time on a datetime property
1527
     *
1528
     * @access protected
1529
     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1530
     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1531
     * @throws ReflectionException
1532
     * @throws InvalidArgumentException
1533
     * @throws InvalidInterfaceException
1534
     * @throws InvalidDataTypeException
1535
     * @throws EE_Error
1536
     */
1537
    protected function _set_time_for($time, $fieldname)
1538
    {
1539
        $this->_set_date_time('T', $time, $fieldname);
1540
    }
1541
1542
1543
    /**
1544
     * sets the date on a datetime property
1545
     *
1546
     * @access protected
1547
     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1548
     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1549
     * @throws ReflectionException
1550
     * @throws InvalidArgumentException
1551
     * @throws InvalidInterfaceException
1552
     * @throws InvalidDataTypeException
1553
     * @throws EE_Error
1554
     */
1555
    protected function _set_date_for($date, $fieldname)
1556
    {
1557
        $this->_set_date_time('D', $date, $fieldname);
1558
    }
1559
1560
1561
    /**
1562
     * This takes care of setting a date or time independently on a given model object property. This method also
1563
     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1564
     *
1565
     * @access protected
1566
     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1567
     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1568
     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1569
     *                                        EE_Datetime_Field property)
1570
     * @throws ReflectionException
1571
     * @throws InvalidArgumentException
1572
     * @throws InvalidInterfaceException
1573
     * @throws InvalidDataTypeException
1574
     * @throws EE_Error
1575
     */
1576
    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1577
    {
1578
        $field = $this->_get_dtt_field_settings($fieldname);
1579
        $field->set_timezone($this->_timezone);
1580
        $field->set_date_format($this->_dt_frmt);
1581
        $field->set_time_format($this->_tm_frmt);
1582
        switch ($what) {
1583
            case 'T' :
1584
                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1585
                    $datetime_value,
1586
                    $this->_fields[ $fieldname ]
1587
                );
1588
                break;
1589
            case 'D' :
1590
                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1591
                    $datetime_value,
1592
                    $this->_fields[ $fieldname ]
1593
                );
1594
                break;
1595
            case 'B' :
1596
                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1597
                break;
1598
        }
1599
        $this->_clear_cached_property($fieldname);
1600
    }
1601
1602
1603
    /**
1604
     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1605
     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1606
     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1607
     * that could lead to some unexpected results!
1608
     *
1609
     * @access public
1610
     * @param string $field_name               This is the name of the field on the object that contains the date/time
1611
     *                                         value being returned.
1612
     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1613
     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1614
     * @param string $prepend                  You can include something to prepend on the timestamp
1615
     * @param string $append                   You can include something to append on the timestamp
1616
     * @throws ReflectionException
1617
     * @throws InvalidArgumentException
1618
     * @throws InvalidInterfaceException
1619
     * @throws InvalidDataTypeException
1620
     * @throws EE_Error
1621
     * @return string timestamp
1622
     */
1623
    public function display_in_my_timezone(
1624
        $field_name,
1625
        $callback = 'get_datetime',
1626
        $args = null,
1627
        $prepend = '',
1628
        $append = ''
1629
    ) {
1630
        $timezone = EEH_DTT_Helper::get_timezone();
1631
        if ($timezone === $this->_timezone) {
1632
            return '';
1633
        }
1634
        $original_timezone = $this->_timezone;
1635
        $this->set_timezone($timezone);
1636
        $fn   = (array) $field_name;
1637
        $args = array_merge($fn, (array) $args);
1638
        if (! method_exists($this, $callback)) {
1639
            throw new EE_Error(
1640
                sprintf(
1641
                    esc_html__(
1642
                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1643
                        'event_espresso'
1644
                    ),
1645
                    $callback
1646
                )
1647
            );
1648
        }
1649
        $args   = (array) $args;
1650
        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1651
        $this->set_timezone($original_timezone);
1652
        return $return;
1653
    }
1654
1655
1656
    /**
1657
     * Deletes this model object.
1658
     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1659
     * override
1660
     * `EE_Base_Class::_delete` NOT this class.
1661
     *
1662
     * @return boolean | int
1663
     * @throws ReflectionException
1664
     * @throws InvalidArgumentException
1665
     * @throws InvalidInterfaceException
1666
     * @throws InvalidDataTypeException
1667
     * @throws EE_Error
1668
     */
1669
    public function delete()
1670
    {
1671
        /**
1672
         * Called just before the `EE_Base_Class::_delete` method call.
1673
         * Note:
1674
         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1675
         * should be aware that `_delete` may not always result in a permanent delete.
1676
         * For example, `EE_Soft_Delete_Base_Class::_delete`
1677
         * soft deletes (trash) the object and does not permanently delete it.
1678
         *
1679
         * @param EE_Base_Class $model_object about to be 'deleted'
1680
         */
1681
        do_action('AHEE__EE_Base_Class__delete__before', $this);
1682
        $result = $this->_delete();
1683
        /**
1684
         * Called just after the `EE_Base_Class::_delete` method call.
1685
         * Note:
1686
         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1687
         * should be aware that `_delete` may not always result in a permanent delete.
1688
         * For example `EE_Soft_Base_Class::_delete`
1689
         * soft deletes (trash) the object and does not permanently delete it.
1690
         *
1691
         * @param EE_Base_Class $model_object that was just 'deleted'
1692
         * @param boolean       $result
1693
         */
1694
        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1695
        return $result;
1696
    }
1697
1698
1699
    /**
1700
     * Calls the specific delete method for the instantiated class.
1701
     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1702
     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1703
     * `EE_Base_Class::delete`
1704
     *
1705
     * @return bool|int
1706
     * @throws ReflectionException
1707
     * @throws InvalidArgumentException
1708
     * @throws InvalidInterfaceException
1709
     * @throws InvalidDataTypeException
1710
     * @throws EE_Error
1711
     */
1712
    protected function _delete()
1713
    {
1714
        return $this->delete_permanently();
1715
    }
1716
1717
1718
    /**
1719
     * Deletes this model object permanently from db
1720
     * (but keep in mind related models may block the delete and return an error)
1721
     *
1722
     * @return bool | int
1723
     * @throws ReflectionException
1724
     * @throws InvalidArgumentException
1725
     * @throws InvalidInterfaceException
1726
     * @throws InvalidDataTypeException
1727
     * @throws EE_Error
1728
     */
1729
    public function delete_permanently()
1730
    {
1731
        /**
1732
         * Called just before HARD deleting a model object
1733
         *
1734
         * @param EE_Base_Class $model_object about to be 'deleted'
1735
         */
1736
        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1737
        $model  = $this->get_model();
1738
        $result = $model->delete_permanently_by_ID($this->ID());
1739
        $this->refresh_cache_of_related_objects();
1740
        /**
1741
         * Called just after HARD deleting a model object
1742
         *
1743
         * @param EE_Base_Class $model_object that was just 'deleted'
1744
         * @param boolean       $result
1745
         */
1746
        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1747
        return $result;
1748
    }
1749
1750
1751
    /**
1752
     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1753
     * related model objects
1754
     *
1755
     * @throws ReflectionException
1756
     * @throws InvalidArgumentException
1757
     * @throws InvalidInterfaceException
1758
     * @throws InvalidDataTypeException
1759
     * @throws EE_Error
1760
     */
1761
    public function refresh_cache_of_related_objects()
1762
    {
1763
        $model = $this->get_model();
1764
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1765
            if (! empty($this->_model_relations[ $relation_name ])) {
1766
                $related_objects = $this->_model_relations[ $relation_name ];
1767
                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1768
                    //this relation only stores a single model object, not an array
1769
                    //but let's make it consistent
1770
                    $related_objects = array($related_objects);
1771
                }
1772
                foreach ($related_objects as $related_object) {
1773
                    //only refresh their cache if they're in memory
1774
                    if ($related_object instanceof EE_Base_Class) {
1775
                        $related_object->clear_cache(
1776
                            $model->get_this_model_name(),
1777
                            $this
1778
                        );
1779
                    }
1780
                }
1781
            }
1782
        }
1783
    }
1784
1785
1786
    /**
1787
     *        Saves this object to the database. An array may be supplied to set some values on this
1788
     * object just before saving.
1789
     *
1790
     * @access public
1791
     * @param array $set_cols_n_values keys are field names, values are their new values,
1792
     *                                 if provided during the save() method (often client code will change the fields'
1793
     *                                 values before calling save)
1794
     * @throws InvalidArgumentException
1795
     * @throws InvalidInterfaceException
1796
     * @throws InvalidDataTypeException
1797
     * @throws EE_Error
1798
     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1799
     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1800
     * @throws ReflectionException
1801
     * @throws ReflectionException
1802
     * @throws ReflectionException
1803
     */
1804
    public function save($set_cols_n_values = array())
1805
    {
1806
        $model = $this->get_model();
1807
        /**
1808
         * Filters the fields we're about to save on the model object
1809
         *
1810
         * @param array         $set_cols_n_values
1811
         * @param EE_Base_Class $model_object
1812
         */
1813
        $set_cols_n_values = (array) apply_filters(
1814
            'FHEE__EE_Base_Class__save__set_cols_n_values',
1815
            $set_cols_n_values,
1816
            $this
1817
        );
1818
        //set attributes as provided in $set_cols_n_values
1819
        foreach ($set_cols_n_values as $column => $value) {
1820
            $this->set($column, $value);
1821
        }
1822
        // no changes ? then don't do anything
1823
        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1824
            return 0;
1825
        }
1826
        /**
1827
         * Saving a model object.
1828
         * Before we perform a save, this action is fired.
1829
         *
1830
         * @param EE_Base_Class $model_object the model object about to be saved.
1831
         */
1832
        do_action('AHEE__EE_Base_Class__save__begin', $this);
1833
        if (! $this->allow_persist()) {
1834
            return 0;
1835
        }
1836
        // now get current attribute values
1837
        $save_cols_n_values = $this->_fields;
1838
        // if the object already has an ID, update it. Otherwise, insert it
1839
        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1840
        // They have been
1841
        $old_assumption_concerning_value_preparation = $model
1842
            ->get_assumption_concerning_values_already_prepared_by_model_object();
1843
        $model->assume_values_already_prepared_by_model_object(true);
1844
        //does this model have an autoincrement PK?
1845
        if ($model->has_primary_key_field()) {
1846
            if ($model->get_primary_key_field()->is_auto_increment()) {
1847
                //ok check if it's set, if so: update; if not, insert
1848
                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1849
                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1850
                } else {
1851
                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1852
                    $results = $model->insert($save_cols_n_values);
1853
                    if ($results) {
1854
                        //if successful, set the primary key
1855
                        //but don't use the normal SET method, because it will check if
1856
                        //an item with the same ID exists in the mapper & db, then
1857
                        //will find it in the db (because we just added it) and THAT object
1858
                        //will get added to the mapper before we can add this one!
1859
                        //but if we just avoid using the SET method, all that headache can be avoided
1860
                        $pk_field_name                   = $model->primary_key_name();
1861
                        $this->_fields[ $pk_field_name ] = $results;
1862
                        $this->_clear_cached_property($pk_field_name);
1863
                        $model->add_to_entity_map($this);
1864
                        $this->_update_cached_related_model_objs_fks();
1865
                    }
1866
                }
1867
            } else {//PK is NOT auto-increment
1868
                //so check if one like it already exists in the db
1869
                if ($model->exists_by_ID($this->ID())) {
1870
                    if (WP_DEBUG && ! $this->in_entity_map()) {
1871
                        throw new EE_Error(
1872
                            sprintf(
1873
                                esc_html__(
1874
                                    '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',
1875
                                    'event_espresso'
1876
                                ),
1877
                                get_class($this),
1878
                                get_class($model) . '::instance()->add_to_entity_map()',
1879
                                get_class($model) . '::instance()->get_one_by_ID()',
1880
                                '<br />'
1881
                            )
1882
                        );
1883
                    }
1884
                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1885
                } else {
1886
                    $results = $model->insert($save_cols_n_values);
1887
                    $this->_update_cached_related_model_objs_fks();
1888
                }
1889
            }
1890
        } else {//there is NO primary key
1891
            $already_in_db = false;
1892
            foreach ($model->unique_indexes() as $index) {
1893
                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1894
                if ($model->exists(array($uniqueness_where_params))) {
1895
                    $already_in_db = true;
1896
                }
1897
            }
1898
            if ($already_in_db) {
1899
                $combined_pk_fields_n_values = array_intersect_key($save_cols_n_values,
1900
                    $model->get_combined_primary_key_fields());
1901
                $results                     = $model->update(
1902
                    $save_cols_n_values,
1903
                    $combined_pk_fields_n_values
1904
                );
1905
            } else {
1906
                $results = $model->insert($save_cols_n_values);
1907
            }
1908
        }
1909
        //restore the old assumption about values being prepared by the model object
1910
        $model->assume_values_already_prepared_by_model_object(
1911
                $old_assumption_concerning_value_preparation
1912
            );
1913
        /**
1914
         * After saving the model object this action is called
1915
         *
1916
         * @param EE_Base_Class $model_object which was just saved
1917
         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1918
         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1919
         */
1920
        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1921
        $this->_has_changes = false;
1922
        return $results;
1923
    }
1924
1925
1926
    /**
1927
     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1928
     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1929
     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1930
     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1931
     * 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
1932
     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1933
     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1934
     *
1935
     * @return void
1936
     * @throws ReflectionException
1937
     * @throws InvalidArgumentException
1938
     * @throws InvalidInterfaceException
1939
     * @throws InvalidDataTypeException
1940
     * @throws EE_Error
1941
     */
1942
    protected function _update_cached_related_model_objs_fks()
1943
    {
1944
        $model = $this->get_model();
1945
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1946
            if ($relation_obj instanceof EE_Has_Many_Relation) {
1947
                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1948
                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1949
                        $model->get_this_model_name()
1950
                    );
1951
                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1952
                    if ($related_model_obj_in_cache->ID()) {
1953
                        $related_model_obj_in_cache->save();
1954
                    }
1955
                }
1956
            }
1957
        }
1958
    }
1959
1960
1961
    /**
1962
     * Saves this model object and its NEW cached relations to the database.
1963
     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1964
     * In order for that to work, we would need to mark model objects as dirty/clean...
1965
     * because otherwise, there's a potential for infinite looping of saving
1966
     * Saves the cached related model objects, and ensures the relation between them
1967
     * and this object and properly setup
1968
     *
1969
     * @return int ID of new model object on save; 0 on failure+
1970
     * @throws ReflectionException
1971
     * @throws InvalidArgumentException
1972
     * @throws InvalidInterfaceException
1973
     * @throws InvalidDataTypeException
1974
     * @throws EE_Error
1975
     */
1976
    public function save_new_cached_related_model_objs()
1977
    {
1978
        //make sure this has been saved
1979
        if (! $this->ID()) {
1980
            $id = $this->save();
1981
        } else {
1982
            $id = $this->ID();
1983
        }
1984
        //now save all the NEW cached model objects  (ie they don't exist in the DB)
1985
        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1986
            if ($this->_model_relations[ $relationName ]) {
1987
                //is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1988
                //or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1989
                /* @var $related_model_obj EE_Base_Class */
1990
                if ($relationObj instanceof EE_Belongs_To_Relation) {
1991
                    //add a relation to that relation type (which saves the appropriate thing in the process)
1992
                    //but ONLY if it DOES NOT exist in the DB
1993
                    $related_model_obj = $this->_model_relations[ $relationName ];
1994
                    //					if( ! $related_model_obj->ID()){
1995
                    $this->_add_relation_to($related_model_obj, $relationName);
1996
                    $related_model_obj->save_new_cached_related_model_objs();
1997
                    //					}
1998
                } else {
1999
                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2000
                        //add a relation to that relation type (which saves the appropriate thing in the process)
2001
                        //but ONLY if it DOES NOT exist in the DB
2002
                        //						if( ! $related_model_obj->ID()){
2003
                        $this->_add_relation_to($related_model_obj, $relationName);
2004
                        $related_model_obj->save_new_cached_related_model_objs();
2005
                        //						}
2006
                    }
2007
                }
2008
            }
2009
        }
2010
        return $id;
2011
    }
2012
2013
2014
    /**
2015
     * for getting a model while instantiated.
2016
     *
2017
     * @return EEM_Base | EEM_CPT_Base
2018
     * @throws ReflectionException
2019
     * @throws InvalidArgumentException
2020
     * @throws InvalidInterfaceException
2021
     * @throws InvalidDataTypeException
2022
     * @throws EE_Error
2023
     */
2024
    public function get_model()
2025
    {
2026
        if (! $this->_model) {
2027
            $modelName    = self::_get_model_classname(get_class($this));
2028
            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2029
        } else {
2030
            $this->_model->set_timezone($this->_timezone);
2031
        }
2032
        return $this->_model;
2033
    }
2034
2035
2036
    /**
2037
     * @param $props_n_values
2038
     * @param $classname
2039
     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2040
     * @throws ReflectionException
2041
     * @throws InvalidArgumentException
2042
     * @throws InvalidInterfaceException
2043
     * @throws InvalidDataTypeException
2044
     * @throws EE_Error
2045
     */
2046
    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2047
    {
2048
        //TODO: will not work for Term_Relationships because they have no PK!
2049
        $primary_id_ref = self::_get_primary_key_name($classname);
2050
        if (
2051
            array_key_exists($primary_id_ref, $props_n_values)
2052
            && ! empty($props_n_values[ $primary_id_ref ])
2053
        ) {
2054
            $id = $props_n_values[ $primary_id_ref ];
2055
            return self::_get_model($classname)->get_from_entity_map($id);
2056
        }
2057
        return false;
2058
    }
2059
2060
2061
    /**
2062
     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2063
     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2064
     * 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
2065
     * we return false.
2066
     *
2067
     * @param  array  $props_n_values   incoming array of properties and their values
2068
     * @param  string $classname        the classname of the child class
2069
     * @param null    $timezone
2070
     * @param array   $date_formats     incoming date_formats in an array where the first value is the
2071
     *                                  date_format and the second value is the time format
2072
     * @return mixed (EE_Base_Class|bool)
2073
     * @throws InvalidArgumentException
2074
     * @throws InvalidInterfaceException
2075
     * @throws InvalidDataTypeException
2076
     * @throws EE_Error
2077
     * @throws ReflectionException
2078
     * @throws ReflectionException
2079
     * @throws ReflectionException
2080
     */
2081
    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2082
    {
2083
        $existing = null;
2084
        $model    = self::_get_model($classname, $timezone);
2085
        if ($model->has_primary_key_field()) {
2086
            $primary_id_ref = self::_get_primary_key_name($classname);
2087
            if (array_key_exists($primary_id_ref, $props_n_values)
2088
                && ! empty($props_n_values[ $primary_id_ref ])
2089
            ) {
2090
                $existing = $model->get_one_by_ID(
2091
                    $props_n_values[ $primary_id_ref ]
2092
                );
2093
            }
2094
        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2095
            //no primary key on this model, but there's still a matching item in the DB
2096
            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2097
                self::_get_model($classname, $timezone)
2098
                    ->get_index_primary_key_string($props_n_values)
2099
            );
2100
        }
2101
        if ($existing) {
2102
            //set date formats if present before setting values
2103
            if (! empty($date_formats) && is_array($date_formats)) {
2104
                $existing->set_date_format($date_formats[0]);
2105
                $existing->set_time_format($date_formats[1]);
2106
            } else {
2107
                //set default formats for date and time
2108
                $existing->set_date_format(get_option('date_format'));
2109
                $existing->set_time_format(get_option('time_format'));
2110
            }
2111
            foreach ($props_n_values as $property => $field_value) {
2112
                $existing->set($property, $field_value);
2113
            }
2114
            return $existing;
2115
        }
2116
        return false;
2117
    }
2118
2119
2120
    /**
2121
     * Gets the EEM_*_Model for this class
2122
     *
2123
     * @access public now, as this is more convenient
2124
     * @param      $classname
2125
     * @param null $timezone
2126
     * @throws ReflectionException
2127
     * @throws InvalidArgumentException
2128
     * @throws InvalidInterfaceException
2129
     * @throws InvalidDataTypeException
2130
     * @throws EE_Error
2131
     * @return EEM_Base
2132
     */
2133
    protected static function _get_model($classname, $timezone = null)
2134
    {
2135
        //find model for this class
2136
        if (! $classname) {
2137
            throw new EE_Error(
2138
                sprintf(
2139
                    esc_html__(
2140
                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2141
                        'event_espresso'
2142
                    ),
2143
                    $classname
2144
                )
2145
            );
2146
        }
2147
        $modelName = self::_get_model_classname($classname);
2148
        return self::_get_model_instance_with_name($modelName, $timezone);
2149
    }
2150
2151
2152
    /**
2153
     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2154
     *
2155
     * @param string $model_classname
2156
     * @param null   $timezone
2157
     * @return EEM_Base
2158
     * @throws ReflectionException
2159
     * @throws InvalidArgumentException
2160
     * @throws InvalidInterfaceException
2161
     * @throws InvalidDataTypeException
2162
     * @throws EE_Error
2163
     */
2164
    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2165
    {
2166
        $model_classname = str_replace('EEM_', '', $model_classname);
2167
        $model           = EE_Registry::instance()->load_model($model_classname);
2168
        $model->set_timezone($timezone);
2169
        return $model;
2170
    }
2171
2172
2173
    /**
2174
     * If a model name is provided (eg Registration), gets the model classname for that model.
2175
     * Also works if a model class's classname is provided (eg EE_Registration).
2176
     *
2177
     * @param null $model_name
2178
     * @return string like EEM_Attendee
2179
     */
2180
    private static function _get_model_classname($model_name = null)
2181
    {
2182
        if (strpos($model_name, 'EE_') === 0) {
2183
            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2184
        } else {
2185
            $model_classname = 'EEM_' . $model_name;
2186
        }
2187
        return $model_classname;
2188
    }
2189
2190
2191
    /**
2192
     * returns the name of the primary key attribute
2193
     *
2194
     * @param null $classname
2195
     * @throws ReflectionException
2196
     * @throws InvalidArgumentException
2197
     * @throws InvalidInterfaceException
2198
     * @throws InvalidDataTypeException
2199
     * @throws EE_Error
2200
     * @return string
2201
     */
2202
    protected static function _get_primary_key_name($classname = null)
2203
    {
2204
        if (! $classname) {
2205
            throw new EE_Error(
2206
                sprintf(
2207
                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2208
                    $classname
2209
                )
2210
            );
2211
        }
2212
        return self::_get_model($classname)->get_primary_key_field()->get_name();
2213
    }
2214
2215
2216
    /**
2217
     * Gets the value of the primary key.
2218
     * If the object hasn't yet been saved, it should be whatever the model field's default was
2219
     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2220
     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2221
     *
2222
     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2223
     * @throws ReflectionException
2224
     * @throws InvalidArgumentException
2225
     * @throws InvalidInterfaceException
2226
     * @throws InvalidDataTypeException
2227
     * @throws EE_Error
2228
     */
2229
    public function ID()
2230
    {
2231
        $model = $this->get_model();
2232
        //now that we know the name of the variable, use a variable variable to get its value and return its
2233
        if ($model->has_primary_key_field()) {
2234
            return $this->_fields[ $model->primary_key_name() ];
2235
        }
2236
        return $model->get_index_primary_key_string($this->_fields);
2237
    }
2238
2239
2240
    /**
2241
     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2242
     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2243
     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2244
     *
2245
     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2246
     * @param string $relationName                     eg 'Events','Question',etc.
2247
     *                                                 an attendee to a group, you also want to specify which role they
2248
     *                                                 will have in that group. So you would use this parameter to
2249
     *                                                 specify array('role-column-name'=>'role-id')
2250
     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2251
     *                                                 allow you to further constrict the relation to being added.
2252
     *                                                 However, keep in mind that the columns (keys) given must match a
2253
     *                                                 column on the JOIN table and currently only the HABTM models
2254
     *                                                 accept these additional conditions.  Also remember that if an
2255
     *                                                 exact match isn't found for these extra cols/val pairs, then a
2256
     *                                                 NEW row is created in the join table.
2257
     * @param null   $cache_id
2258
     * @throws ReflectionException
2259
     * @throws InvalidArgumentException
2260
     * @throws InvalidInterfaceException
2261
     * @throws InvalidDataTypeException
2262
     * @throws EE_Error
2263
     * @return EE_Base_Class the object the relation was added to
2264
     */
2265
    public function _add_relation_to(
2266
        $otherObjectModelObjectOrID,
2267
        $relationName,
2268
        $extra_join_model_fields_n_values = array(),
2269
        $cache_id = null
2270
    ) {
2271
        $model = $this->get_model();
2272
        //if this thing exists in the DB, save the relation to the DB
2273
        if ($this->ID()) {
2274
            $otherObject = $model->add_relationship_to(
2275
                $this,
2276
                $otherObjectModelObjectOrID,
2277
                $relationName,
2278
                $extra_join_model_fields_n_values
2279
            );
2280
            //clear cache so future get_many_related and get_first_related() return new results.
2281
            $this->clear_cache($relationName, $otherObject, true);
2282
            if ($otherObject instanceof EE_Base_Class) {
2283
                $otherObject->clear_cache($model->get_this_model_name(), $this);
2284
            }
2285
        } else {
2286
            //this thing doesn't exist in the DB,  so just cache it
2287 View Code Duplication
            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2288
                throw new EE_Error(
2289
                    sprintf(
2290
                        esc_html__(
2291
                            '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',
2292
                            'event_espresso'
2293
                        ),
2294
                        $otherObjectModelObjectOrID,
2295
                        get_class($this)
2296
                    )
2297
                );
2298
            }
2299
            $otherObject = $otherObjectModelObjectOrID;
2300
            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2301
        }
2302
        if ($otherObject instanceof EE_Base_Class) {
2303
            //fix the reciprocal relation too
2304
            if ($otherObject->ID()) {
2305
                //its saved so assumed relations exist in the DB, so we can just
2306
                //clear the cache so future queries use the updated info in the DB
2307
                $otherObject->clear_cache(
2308
                    $model->get_this_model_name(),
2309
                    null,
2310
                    true
2311
                );
2312
            } else {
2313
                //it's not saved, so it caches relations like this
2314
                $otherObject->cache($model->get_this_model_name(), $this);
2315
            }
2316
        }
2317
        return $otherObject;
2318
    }
2319
2320
2321
    /**
2322
     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2323
     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2324
     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2325
     * from the cache
2326
     *
2327
     * @param mixed  $otherObjectModelObjectOrID
2328
     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2329
     *                to the DB yet
2330
     * @param string $relationName
2331
     * @param array  $where_query
2332
     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2333
     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2334
     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2335
     *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2336
     *                created in the join table.
2337
     * @return EE_Base_Class the relation was removed from
2338
     * @throws ReflectionException
2339
     * @throws InvalidArgumentException
2340
     * @throws InvalidInterfaceException
2341
     * @throws InvalidDataTypeException
2342
     * @throws EE_Error
2343
     */
2344
    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2345
    {
2346
        if ($this->ID()) {
2347
            //if this exists in the DB, save the relation change to the DB too
2348
            $otherObject = $this->get_model()->remove_relationship_to(
2349
                $this,
2350
                $otherObjectModelObjectOrID,
2351
                $relationName,
2352
                $where_query
2353
            );
2354
            $this->clear_cache(
2355
                $relationName,
2356
                $otherObject
2357
            );
2358
        } else {
2359
            //this doesn't exist in the DB, just remove it from the cache
2360
            $otherObject = $this->clear_cache(
2361
                $relationName,
2362
                $otherObjectModelObjectOrID
2363
            );
2364
        }
2365
        if ($otherObject instanceof EE_Base_Class) {
2366
            $otherObject->clear_cache(
2367
                $this->get_model()->get_this_model_name(),
2368
                $this
2369
            );
2370
        }
2371
        return $otherObject;
2372
    }
2373
2374
2375
    /**
2376
     * Removes ALL the related things for the $relationName.
2377
     *
2378
     * @param string $relationName
2379
     * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
2380
     * @return EE_Base_Class
2381
     * @throws ReflectionException
2382
     * @throws InvalidArgumentException
2383
     * @throws InvalidInterfaceException
2384
     * @throws InvalidDataTypeException
2385
     * @throws EE_Error
2386
     */
2387
    public function _remove_relations($relationName, $where_query_params = array())
2388
    {
2389
        if ($this->ID()) {
2390
            //if this exists in the DB, save the relation change to the DB too
2391
            $otherObjects = $this->get_model()->remove_relations(
2392
                $this,
2393
                $relationName,
2394
                $where_query_params
2395
            );
2396
            $this->clear_cache(
2397
                $relationName,
2398
                null,
2399
                true
2400
            );
2401
        } else {
2402
            //this doesn't exist in the DB, just remove it from the cache
2403
            $otherObjects = $this->clear_cache(
2404
                $relationName,
2405
                null,
2406
                true
2407
            );
2408
        }
2409
        if (is_array($otherObjects)) {
2410
            foreach ($otherObjects as $otherObject) {
2411
                $otherObject->clear_cache(
2412
                    $this->get_model()->get_this_model_name(),
2413
                    $this
2414
                );
2415
            }
2416
        }
2417
        return $otherObjects;
2418
    }
2419
2420
2421
    /**
2422
     * Gets all the related model objects of the specified type. Eg, if the current class if
2423
     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2424
     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2425
     * because we want to get even deleted items etc.
2426
     *
2427
     * @param string $relationName key in the model's _model_relations array
2428
     * @param array  $query_params like EEM_Base::get_all
2429
     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2430
     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2431
     *                             results if you want IDs
2432
     * @throws ReflectionException
2433
     * @throws InvalidArgumentException
2434
     * @throws InvalidInterfaceException
2435
     * @throws InvalidDataTypeException
2436
     * @throws EE_Error
2437
     */
2438
    public function get_many_related($relationName, $query_params = array())
2439
    {
2440
        if ($this->ID()) {
2441
            //this exists in the DB, so get the related things from either the cache or the DB
2442
            //if there are query parameters, forget about caching the related model objects.
2443
            if ($query_params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query_params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2444
                $related_model_objects = $this->get_model()->get_all_related(
2445
                    $this,
2446
                    $relationName,
2447
                    $query_params
2448
                );
2449
            } else {
2450
                //did we already cache the result of this query?
2451
                $cached_results = $this->get_all_from_cache($relationName);
2452
                if (! $cached_results) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cached_results of type EE_Base_Class[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2453
                    $related_model_objects = $this->get_model()->get_all_related(
2454
                        $this,
2455
                        $relationName,
2456
                        $query_params
2457
                    );
2458
                    //if no query parameters were passed, then we got all the related model objects
2459
                    //for that relation. We can cache them then.
2460
                    foreach ($related_model_objects as $related_model_object) {
2461
                        $this->cache($relationName, $related_model_object);
2462
                    }
2463
                } else {
2464
                    $related_model_objects = $cached_results;
2465
                }
2466
            }
2467
        } else {
2468
            //this doesn't exist in the DB, so just get the related things from the cache
2469
            $related_model_objects = $this->get_all_from_cache($relationName);
2470
        }
2471
        return $related_model_objects;
2472
    }
2473
2474
2475
    /**
2476
     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2477
     * unless otherwise specified in the $query_params
2478
     *
2479
     * @param string $relation_name  model_name like 'Event', or 'Registration'
2480
     * @param array  $query_params   like EEM_Base::get_all's
2481
     * @param string $field_to_count name of field to count by. By default, uses primary key
2482
     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2483
     *                               that by the setting $distinct to TRUE;
2484
     * @return int
2485
     * @throws ReflectionException
2486
     * @throws InvalidArgumentException
2487
     * @throws InvalidInterfaceException
2488
     * @throws InvalidDataTypeException
2489
     * @throws EE_Error
2490
     */
2491
    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2492
    {
2493
        return $this->get_model()->count_related(
2494
            $this,
2495
            $relation_name,
2496
            $query_params,
2497
            $field_to_count,
2498
            $distinct
2499
        );
2500
    }
2501
2502
2503
    /**
2504
     * Instead of getting the related model objects, simply sums up the values of the specified field.
2505
     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2506
     *
2507
     * @param string $relation_name model_name like 'Event', or 'Registration'
2508
     * @param array  $query_params  like EEM_Base::get_all's
2509
     * @param string $field_to_sum  name of field to count by.
2510
     *                              By default, uses primary key
2511
     *                              (which doesn't make much sense, so you should probably change it)
2512
     * @return int
2513
     * @throws ReflectionException
2514
     * @throws InvalidArgumentException
2515
     * @throws InvalidInterfaceException
2516
     * @throws InvalidDataTypeException
2517
     * @throws EE_Error
2518
     */
2519
    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2520
    {
2521
        return $this->get_model()->sum_related(
2522
            $this,
2523
            $relation_name,
2524
            $query_params,
2525
            $field_to_sum
2526
        );
2527
    }
2528
2529
2530
    /**
2531
     * Gets the first (ie, one) related model object of the specified type.
2532
     *
2533
     * @param string $relationName key in the model's _model_relations array
2534
     * @param array  $query_params like EEM_Base::get_all
2535
     * @return EE_Base_Class (not an array, a single object)
2536
     * @throws ReflectionException
2537
     * @throws InvalidArgumentException
2538
     * @throws InvalidInterfaceException
2539
     * @throws InvalidDataTypeException
2540
     * @throws EE_Error
2541
     */
2542
    public function get_first_related($relationName, $query_params = array())
2543
    {
2544
        $model = $this->get_model();
2545
        if ($this->ID()) {//this exists in the DB, get from the cache OR the DB
2546
            //if they've provided some query parameters, don't bother trying to cache the result
2547
            //also make sure we're not caching the result of get_first_related
2548
            //on a relation which should have an array of objects (because the cache might have an array of objects)
2549
            if ($query_params
0 ignored issues
show
Bug Best Practice introduced by
The expression $query_params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2550
                || ! $model->related_settings_for($relationName)
2551
                     instanceof
2552
                     EE_Belongs_To_Relation
2553
            ) {
2554
                $related_model_object = $model->get_first_related(
2555
                    $this,
2556
                    $relationName,
2557
                    $query_params
2558
                );
2559
            } else {
2560
                //first, check if we've already cached the result of this query
2561
                $cached_result = $this->get_one_from_cache($relationName);
2562
                if (! $cached_result) {
2563
                    $related_model_object = $model->get_first_related(
2564
                        $this,
2565
                        $relationName,
2566
                        $query_params
2567
                    );
2568
                    $this->cache($relationName, $related_model_object);
2569
                } else {
2570
                    $related_model_object = $cached_result;
2571
                }
2572
            }
2573
        } else {
2574
            $related_model_object = null;
2575
            // this doesn't exist in the Db,
2576
            // but maybe the relation is of type belongs to, and so the related thing might
2577
            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2578
                $related_model_object = $model->get_first_related(
2579
                    $this,
2580
                    $relationName,
2581
                    $query_params
2582
                );
2583
            }
2584
            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2585
            // just get what's cached on this object
2586
            if (! $related_model_object) {
2587
                $related_model_object = $this->get_one_from_cache($relationName);
2588
            }
2589
        }
2590
        return $related_model_object;
2591
    }
2592
2593
2594
    /**
2595
     * Does a delete on all related objects of type $relationName and removes
2596
     * the current model object's relation to them. If they can't be deleted (because
2597
     * of blocking related model objects) does nothing. If the related model objects are
2598
     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2599
     * If this model object doesn't exist yet in the DB, just removes its related things
2600
     *
2601
     * @param string $relationName
2602
     * @param array  $query_params like EEM_Base::get_all's
2603
     * @return int how many deleted
2604
     * @throws ReflectionException
2605
     * @throws InvalidArgumentException
2606
     * @throws InvalidInterfaceException
2607
     * @throws InvalidDataTypeException
2608
     * @throws EE_Error
2609
     */
2610 View Code Duplication
    public function delete_related($relationName, $query_params = array())
2611
    {
2612
        if ($this->ID()) {
2613
            $count = $this->get_model()->delete_related(
2614
                $this,
2615
                $relationName,
2616
                $query_params
2617
            );
2618
        } else {
2619
            $count = count($this->get_all_from_cache($relationName));
2620
            $this->clear_cache($relationName, null, true);
2621
        }
2622
        return $count;
2623
    }
2624
2625
2626
    /**
2627
     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2628
     * the current model object's relation to them. If they can't be deleted (because
2629
     * of blocking related model objects) just does a soft delete on it instead, if possible.
2630
     * If the related thing isn't a soft-deletable model object, this function is identical
2631
     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2632
     *
2633
     * @param string $relationName
2634
     * @param array  $query_params like EEM_Base::get_all's
2635
     * @return int how many deleted (including those soft deleted)
2636
     * @throws ReflectionException
2637
     * @throws InvalidArgumentException
2638
     * @throws InvalidInterfaceException
2639
     * @throws InvalidDataTypeException
2640
     * @throws EE_Error
2641
     */
2642 View Code Duplication
    public function delete_related_permanently($relationName, $query_params = array())
2643
    {
2644
        if ($this->ID()) {
2645
            $count = $this->get_model()->delete_related_permanently(
2646
                $this,
2647
                $relationName,
2648
                $query_params
2649
            );
2650
        } else {
2651
            $count = count($this->get_all_from_cache($relationName));
2652
        }
2653
        $this->clear_cache($relationName, null, true);
2654
        return $count;
2655
    }
2656
2657
2658
    /**
2659
     * is_set
2660
     * Just a simple utility function children can use for checking if property exists
2661
     *
2662
     * @access  public
2663
     * @param  string $field_name property to check
2664
     * @return bool                              TRUE if existing,FALSE if not.
2665
     */
2666
    public function is_set($field_name)
2667
    {
2668
        return isset($this->_fields[ $field_name ]);
2669
    }
2670
2671
2672
    /**
2673
     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2674
     * EE_Error exception if they don't
2675
     *
2676
     * @param  mixed (string|array) $properties properties to check
2677
     * @throws EE_Error
2678
     * @return bool                              TRUE if existing, throw EE_Error if not.
2679
     */
2680
    protected function _property_exists($properties)
2681
    {
2682
        foreach ((array) $properties as $property_name) {
2683
            //first make sure this property exists
2684
            if (! $this->_fields[ $property_name ]) {
2685
                throw new EE_Error(
2686
                    sprintf(
2687
                        esc_html__(
2688
                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2689
                            'event_espresso'
2690
                        ),
2691
                        $property_name
2692
                    )
2693
                );
2694
            }
2695
        }
2696
        return true;
2697
    }
2698
2699
2700
    /**
2701
     * This simply returns an array of model fields for this object
2702
     *
2703
     * @return array
2704
     * @throws ReflectionException
2705
     * @throws InvalidArgumentException
2706
     * @throws InvalidInterfaceException
2707
     * @throws InvalidDataTypeException
2708
     * @throws EE_Error
2709
     */
2710
    public function model_field_array()
2711
    {
2712
        $fields     = $this->get_model()->field_settings(false);
2713
        $properties = array();
2714
        //remove prepended underscore
2715
        foreach ($fields as $field_name => $settings) {
2716
            $properties[ $field_name ] = $this->get($field_name);
2717
        }
2718
        return $properties;
2719
    }
2720
2721
2722
    /**
2723
     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2724
     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2725
     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2726
     * Instead of requiring a plugin to extend the EE_Base_Class
2727
     * (which works fine is there's only 1 plugin, but when will that happen?)
2728
     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2729
     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2730
     * and accepts 2 arguments: the object on which the function was called,
2731
     * and an array of the original arguments passed to the function.
2732
     * Whatever their callback function returns will be returned by this function.
2733
     * Example: in functions.php (or in a plugin):
2734
     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2735
     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2736
     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2737
     *          return $previousReturnValue.$returnString;
2738
     *      }
2739
     * require('EE_Answer.class.php');
2740
     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2741
     * echo $answer->my_callback('monkeys',100);
2742
     * //will output "you called my_callback! and passed args:monkeys,100"
2743
     *
2744
     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2745
     * @param array  $args       array of original arguments passed to the function
2746
     * @throws EE_Error
2747
     * @return mixed whatever the plugin which calls add_filter decides
2748
     */
2749
    public function __call($methodName, $args)
2750
    {
2751
        $className = get_class($this);
2752
        $tagName   = "FHEE__{$className}__{$methodName}";
2753
        if (! has_filter($tagName)) {
2754
            throw new EE_Error(
2755
                sprintf(
2756
                    esc_html__(
2757
                        "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;}",
2758
                        'event_espresso'
2759
                    ),
2760
                    $methodName,
2761
                    $className,
2762
                    $tagName
2763
                )
2764
            );
2765
        }
2766
        return apply_filters($tagName, null, $this, $args);
2767
    }
2768
2769
2770
    /**
2771
     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2772
     * A $previous_value can be specified in case there are many meta rows with the same key
2773
     *
2774
     * @param string $meta_key
2775
     * @param mixed  $meta_value
2776
     * @param mixed  $previous_value
2777
     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2778
     *                  NOTE: if the values haven't changed, returns 0
2779
     * @throws InvalidArgumentException
2780
     * @throws InvalidInterfaceException
2781
     * @throws InvalidDataTypeException
2782
     * @throws EE_Error
2783
     * @throws ReflectionException
2784
     */
2785
    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2786
    {
2787
        $query_params = array(
2788
            array(
2789
                'EXM_key'  => $meta_key,
2790
                'OBJ_ID'   => $this->ID(),
2791
                'EXM_type' => $this->get_model()->get_this_model_name(),
2792
            ),
2793
        );
2794
        if ($previous_value !== null) {
2795
            $query_params[0]['EXM_value'] = $meta_value;
2796
        }
2797
        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2798
        if (! $existing_rows_like_that) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $existing_rows_like_that of type EE_Base_Class[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2799
            return $this->add_extra_meta($meta_key, $meta_value);
2800
        }
2801
        foreach ($existing_rows_like_that as $existing_row) {
2802
            $existing_row->save(array('EXM_value' => $meta_value));
2803
        }
2804
        return count($existing_rows_like_that);
2805
    }
2806
2807
2808
    /**
2809
     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2810
     * no other extra meta for this model object have the same key. Returns TRUE if the
2811
     * extra meta row was entered, false if not
2812
     *
2813
     * @param string  $meta_key
2814
     * @param mixed   $meta_value
2815
     * @param boolean $unique
2816
     * @return boolean
2817
     * @throws InvalidArgumentException
2818
     * @throws InvalidInterfaceException
2819
     * @throws InvalidDataTypeException
2820
     * @throws EE_Error
2821
     * @throws ReflectionException
2822
     * @throws ReflectionException
2823
     */
2824
    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2825
    {
2826
        if ($unique) {
2827
            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2828
                array(
2829
                    array(
2830
                        'EXM_key'  => $meta_key,
2831
                        'OBJ_ID'   => $this->ID(),
2832
                        'EXM_type' => $this->get_model()->get_this_model_name(),
2833
                    ),
2834
                )
2835
            );
2836
            if ($existing_extra_meta) {
2837
                return false;
2838
            }
2839
        }
2840
        $new_extra_meta = EE_Extra_Meta::new_instance(
2841
            array(
2842
                'EXM_key'   => $meta_key,
2843
                'EXM_value' => $meta_value,
2844
                'OBJ_ID'    => $this->ID(),
2845
                'EXM_type'  => $this->get_model()->get_this_model_name(),
2846
            )
2847
        );
2848
        $new_extra_meta->save();
2849
        return true;
2850
    }
2851
2852
2853
    /**
2854
     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2855
     * is specified, only deletes extra meta records with that value.
2856
     *
2857
     * @param string $meta_key
2858
     * @param mixed  $meta_value
2859
     * @return int number of extra meta rows deleted
2860
     * @throws InvalidArgumentException
2861
     * @throws InvalidInterfaceException
2862
     * @throws InvalidDataTypeException
2863
     * @throws EE_Error
2864
     * @throws ReflectionException
2865
     */
2866
    public function delete_extra_meta($meta_key, $meta_value = null)
2867
    {
2868
        $query_params = array(
2869
            array(
2870
                'EXM_key'  => $meta_key,
2871
                'OBJ_ID'   => $this->ID(),
2872
                'EXM_type' => $this->get_model()->get_this_model_name(),
2873
            ),
2874
        );
2875
        if ($meta_value !== null) {
2876
            $query_params[0]['EXM_value'] = $meta_value;
2877
        }
2878
        return EEM_Extra_Meta::instance()->delete($query_params);
2879
    }
2880
2881
2882
    /**
2883
     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2884
     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2885
     * You can specify $default is case you haven't found the extra meta
2886
     *
2887
     * @param string  $meta_key
2888
     * @param boolean $single
2889
     * @param mixed   $default if we don't find anything, what should we return?
2890
     * @return mixed single value if $single; array if ! $single
2891
     * @throws ReflectionException
2892
     * @throws InvalidArgumentException
2893
     * @throws InvalidInterfaceException
2894
     * @throws InvalidDataTypeException
2895
     * @throws EE_Error
2896
     */
2897
    public function get_extra_meta($meta_key, $single = false, $default = null)
2898
    {
2899
        if ($single) {
2900
            $result = $this->get_first_related(
2901
                'Extra_Meta',
2902
                array(array('EXM_key' => $meta_key))
2903
            );
2904
            if ($result instanceof EE_Extra_Meta) {
2905
                return $result->value();
2906
            }
2907
        } else {
2908
            $results = $this->get_many_related(
2909
                'Extra_Meta',
2910
                array(array('EXM_key' => $meta_key))
2911
            );
2912
            if ($results) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $results of type EE_Base_Class[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2913
                $values = array();
2914
                foreach ($results as $result) {
2915
                    if ($result instanceof EE_Extra_Meta) {
2916
                        $values[ $result->ID() ] = $result->value();
2917
                    }
2918
                }
2919
                return $values;
2920
            }
2921
        }
2922
        //if nothing discovered yet return default.
2923
        return apply_filters(
2924
            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2925
            $default,
2926
            $meta_key,
2927
            $single,
2928
            $this
2929
        );
2930
    }
2931
2932
2933
    /**
2934
     * Returns a simple array of all the extra meta associated with this model object.
2935
     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2936
     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2937
     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2938
     * If $one_of_each_key is false, it will return an array with the top-level keys being
2939
     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2940
     * finally the extra meta's value as each sub-value. (eg
2941
     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2942
     *
2943
     * @param boolean $one_of_each_key
2944
     * @return array
2945
     * @throws ReflectionException
2946
     * @throws InvalidArgumentException
2947
     * @throws InvalidInterfaceException
2948
     * @throws InvalidDataTypeException
2949
     * @throws EE_Error
2950
     */
2951
    public function all_extra_meta_array($one_of_each_key = true)
2952
    {
2953
        $return_array = array();
2954
        if ($one_of_each_key) {
2955
            $extra_meta_objs = $this->get_many_related(
2956
                'Extra_Meta',
2957
                array('group_by' => 'EXM_key')
2958
            );
2959
            foreach ($extra_meta_objs as $extra_meta_obj) {
2960
                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2961
                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2962
                }
2963
            }
2964
        } else {
2965
            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2966
            foreach ($extra_meta_objs as $extra_meta_obj) {
2967
                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2968
                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2969
                        $return_array[ $extra_meta_obj->key() ] = array();
2970
                    }
2971
                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2972
                }
2973
            }
2974
        }
2975
        return $return_array;
2976
    }
2977
2978
2979
    /**
2980
     * Gets a pretty nice displayable nice for this model object. Often overridden
2981
     *
2982
     * @return string
2983
     * @throws ReflectionException
2984
     * @throws InvalidArgumentException
2985
     * @throws InvalidInterfaceException
2986
     * @throws InvalidDataTypeException
2987
     * @throws EE_Error
2988
     */
2989
    public function name()
2990
    {
2991
        //find a field that's not a text field
2992
        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2993
        if ($field_we_can_use) {
2994
            return $this->get($field_we_can_use->get_name());
2995
        }
2996
        $first_few_properties = $this->model_field_array();
2997
        $first_few_properties = array_slice($first_few_properties, 0, 3);
2998
        $name_parts           = array();
2999
        foreach ($first_few_properties as $name => $value) {
3000
            $name_parts[] = "$name:$value";
3001
        }
3002
        return implode(',', $name_parts);
3003
    }
3004
3005
3006
    /**
3007
     * in_entity_map
3008
     * Checks if this model object has been proven to already be in the entity map
3009
     *
3010
     * @return boolean
3011
     * @throws ReflectionException
3012
     * @throws InvalidArgumentException
3013
     * @throws InvalidInterfaceException
3014
     * @throws InvalidDataTypeException
3015
     * @throws EE_Error
3016
     */
3017
    public function in_entity_map()
3018
    {
3019
        // well, if we looked, did we find it in the entity map?
3020
        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3021
    }
3022
3023
3024
    /**
3025
     * refresh_from_db
3026
     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3027
     *
3028
     * @throws ReflectionException
3029
     * @throws InvalidArgumentException
3030
     * @throws InvalidInterfaceException
3031
     * @throws InvalidDataTypeException
3032
     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3033
     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3034
     */
3035
    public function refresh_from_db()
3036
    {
3037
        if ($this->ID() && $this->in_entity_map()) {
3038
            $this->get_model()->refresh_entity_map_from_db($this->ID());
3039
        } else {
3040
            //if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3041
            //if it has an ID but it's not in the map, and you're asking me to refresh it
3042
            //that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3043
            //absolutely nothing in it for this ID
3044
            if (WP_DEBUG) {
3045
                throw new EE_Error(
3046
                    sprintf(
3047
                        esc_html__('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.',
3048
                            'event_espresso'),
3049
                        $this->ID(),
3050
                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3051
                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3052
                    )
3053
                );
3054
            }
3055
        }
3056
    }
3057
3058
3059
    /**
3060
     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3061
     * (probably a bad assumption they have made, oh well)
3062
     *
3063
     * @return string
3064
     */
3065
    public function __toString()
3066
    {
3067
        try {
3068
            return sprintf('%s (%s)', $this->name(), $this->ID());
3069
        } catch (Exception $e) {
3070
            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3071
            return '';
3072
        }
3073
    }
3074
3075
3076
    /**
3077
     * Clear related model objects if they're already in the DB, because otherwise when we
3078
     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3079
     * This means if we have made changes to those related model objects, and want to unserialize
3080
     * the this model object on a subsequent request, changes to those related model objects will be lost.
3081
     * Instead, those related model objects should be directly serialized and stored.
3082
     * Eg, the following won't work:
3083
     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3084
     * $att = $reg->attendee();
3085
     * $att->set( 'ATT_fname', 'Dirk' );
3086
     * update_option( 'my_option', serialize( $reg ) );
3087
     * //END REQUEST
3088
     * //START NEXT REQUEST
3089
     * $reg = get_option( 'my_option' );
3090
     * $reg->attendee()->save();
3091
     * And would need to be replace with:
3092
     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3093
     * $att = $reg->attendee();
3094
     * $att->set( 'ATT_fname', 'Dirk' );
3095
     * update_option( 'my_option', serialize( $reg ) );
3096
     * //END REQUEST
3097
     * //START NEXT REQUEST
3098
     * $att = get_option( 'my_option' );
3099
     * $att->save();
3100
     *
3101
     * @return array
3102
     * @throws ReflectionException
3103
     * @throws InvalidArgumentException
3104
     * @throws InvalidInterfaceException
3105
     * @throws InvalidDataTypeException
3106
     * @throws EE_Error
3107
     */
3108
    public function __sleep()
3109
    {
3110
        $model = $this->get_model();
3111
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3112
            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3113
                $classname = 'EE_' . $model->get_this_model_name();
3114
                if (
3115
                    $this->get_one_from_cache($relation_name) instanceof $classname
3116
                    && $this->get_one_from_cache($relation_name)->ID()
3117
                ) {
3118
                    $this->clear_cache(
3119
                        $relation_name,
3120
                        $this->get_one_from_cache($relation_name)->ID()
3121
                    );
3122
                }
3123
            }
3124
        }
3125
        $this->_props_n_values_provided_in_constructor = array();
3126
        $properties_to_serialize                       = get_object_vars($this);
3127
        //don't serialize the model. It's big and that risks recursion
3128
        unset($properties_to_serialize['_model']);
3129
        return array_keys($properties_to_serialize);
3130
    }
3131
3132
3133
    /**
3134
     * restore _props_n_values_provided_in_constructor
3135
     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3136
     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3137
     * At best, you would only be able to detect if state change has occurred during THIS request.
3138
     */
3139
    public function __wakeup()
3140
    {
3141
        $this->_props_n_values_provided_in_constructor = $this->_fields;
3142
    }
3143
}
3144
3145
3146