Completed
Branch FET-11170-model-use-money-enti... (303cb4)
by
unknown
68:01 queued 57:04
created

EE_Base_Class::cache()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 45
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 24
nc 12
nop 3
dl 0
loc 45
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\domain\values\currency\Money;
4
use EventEspresso\core\exceptions\InvalidEntityException;
5
6
if ( ! defined('EVENT_ESPRESSO_VERSION')) {
7
    exit('No direct script access allowed');
8
}
9
do_action('AHEE_log', __FILE__, ' FILE LOADED', '');
10
11
12
13
/**
14
 * Event Espresso
15
 * Event Registration and Management Plugin for WordPress
16
 * @ package            Event Espresso
17
 * @ author                Seth Shoultes
18
 * @ copyright        (c) 2008-2011 Event Espresso  All Rights Reserved.
19
 * @ license            http://eventespresso.com/support/terms-conditions/   * see Plugin Licensing *
20
 * @ link                    http://www.eventespresso.com
21
 * @ version            4.0
22
 * ------------------------------------------------------------------------
23
 * EE_Base_Class class
24
 *
25
 * @package                   Event Espresso
26
 * @subpackage                includes/classes/EE_Base_Class.class.php
27
 * @author                    Michael Nelson
28
 *                            ------------------------------------------------------------------------
29
 */
30
abstract class EE_Base_Class
31
{
32
33
    /**
34
     * This is an array of the original properties and values provided during construction
35
     * of this model object. (keys are model field names, values are their values).
36
     * This list is important to remember so that when we are merging data from the db, we know
37
     * which values to override and which to not override.
38
     *
39
     * @var array
40
     */
41
    protected $_props_n_values_provided_in_constructor;
42
43
    /**
44
     * Timezone
45
     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
46
     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
47
     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
48
     * access to it.
49
     *
50
     * @var string
51
     */
52
    protected $_timezone;
53
54
55
56
    /**
57
     * date format
58
     * pattern or format for displaying dates
59
     *
60
     * @var string $_dt_frmt
61
     */
62
    protected $_dt_frmt;
63
64
65
66
    /**
67
     * time format
68
     * pattern or format for displaying time
69
     *
70
     * @var string $_tm_frmt
71
     */
72
    protected $_tm_frmt;
73
74
75
76
    /**
77
     * This property is for holding a cached array of object properties indexed by property name as the key.
78
     * The purpose of this is for setting a cache on properties that may have calculated values after a
79
     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
80
     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
81
     *
82
     * @var array
83
     */
84
    protected $_cached_properties = array();
85
86
    /**
87
     * An array containing keys of the related model, and values are either an array of related mode objects or a
88
     * single
89
     * related model object. see the model's _model_relations. The keys should match those specified. And if the
90
     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
91
     * all others have an array)
92
     *
93
     * @var array
94
     */
95
    protected $_model_relations = array();
96
97
    /**
98
     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
99
     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
100
     *
101
     * @var array
102
     */
103
    protected $_fields = array();
104
105
    /**
106
     * @var boolean indicating whether or not this model object is intended to ever be saved
107
     * For example, we might create model objects intended to only be used for the duration
108
     * of this request and to be thrown away, and if they were accidentally saved
109
     * it would be a bug.
110
     */
111
    protected $_allow_persist = true;
112
113
    /**
114
     * @var boolean indicating whether or not this model object's properties have changed since construction
115
     */
116
    protected $_has_changes = false;
117
118
    /**
119
     * @var EEM_Base
120
     */
121
    protected $_model;
122
123
124
125
    /**
126
     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
127
     * play nice
128
     *
129
     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
130
     *                                                         layer of the model's _fields array, (eg, EVT_ID,
131
     *                                                         TXN_amount, QST_name, etc) and values are their values
132
     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
133
     *                                                         corresponding db model or not.
134
     * @param string  $timezone                                indicate what timezone you want any datetime fields to
135
     *                                                         be in when instantiating a EE_Base_Class object.
136
     * @param array   $date_formats                            An array of date formats to set on construct where first
137
     *                                                         value is the date_format and second value is the time
138
     *                                                         format.
139
     * @throws EE_Error
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
        // EEH_Debug_Tools::printr( $fieldValues, '$fieldValues  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
150
        // verify client code has not passed any invalid field names
151
        foreach ($fieldValues as $field_name => $field_value) {
152 View Code Duplication
            if ( ! isset($model_fields[$field_name])) {
153
                throw new EE_Error(sprintf(__("Invalid field (%s) passed to constructor of %s. Allowed fields are :%s",
154
                    "event_espresso"), $field_name, get_class($this), implode(", ", array_keys($model_fields))));
155
            }
156
        }
157
        // EEH_Debug_Tools::printr( $model_fields, '$model_fields  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
158
        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
159
        if ( ! empty($date_formats) && is_array($date_formats)) {
160
            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
161
        } else {
162
            //set default formats for date and time
163
            $this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
164
            $this->_tm_frmt = (string)get_option('time_format', 'g:i a');
165
        }
166
        //if db model is instantiating
167
        if ($bydb) {
168
            //client code has indicated these field values are from the database
169 View Code Duplication
            foreach ($model_fields as $fieldName => $field) {
170
                $this->set_from_db($fieldName, isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null);
171
            }
172
        } else {
173
            //we're constructing a brand
174
            //new instance of the model object. Generally, this means we'll need to do more field validation
175 View Code Duplication
            foreach ($model_fields as $fieldName => $field) {
176
                $this->set($fieldName, isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null, true);
177
            }
178
        }
179
        //remember what values were passed to this constructor
180
        $this->_props_n_values_provided_in_constructor = $fieldValues;
181
        //remember in entity mapper
182
        if ( ! $bydb && $model->has_primary_key_field() && $this->ID()) {
183
            $model->add_to_entity_map($this);
184
        }
185
        //setup all the relations
186
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
187
            if ($relation_obj instanceof EE_Belongs_To_Relation) {
188
                $this->_model_relations[$relation_name] = null;
189
            } else {
190
                $this->_model_relations[$relation_name] = array();
191
            }
192
        }
193
        /**
194
         * Action done at the end of each model object construction
195
         *
196
         * @param EE_Base_Class $this the model object just created
197
         */
198
        do_action('AHEE__EE_Base_Class__construct__finished', $this);
199
    }
200
201
202
203
    /**
204
     * Gets whether or not this model object is allowed to persist/be saved to the database.
205
     *
206
     * @return boolean
207
     */
208
    public function allow_persist()
209
    {
210
        return $this->_allow_persist;
211
    }
212
213
214
215
    /**
216
     * Sets whether or not this model object should be allowed to be saved to the DB.
217
     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
218
     * you got new information that somehow made you change your mind.
219
     *
220
     * @param boolean $allow_persist
221
     * @return boolean
222
     */
223
    public function set_allow_persist($allow_persist)
224
    {
225
        return $this->_allow_persist = $allow_persist;
226
    }
227
228
229
230
    /**
231
     * Gets the field's original value when this object was constructed during this request.
232
     * This can be helpful when determining if a model object has changed or not
233
     *
234
     * @param string $field_name
235
     * @return mixed|null
236
     * @throws \EE_Error
237
     */
238
    public function get_original($field_name)
239
    {
240
        if (isset($this->_props_n_values_provided_in_constructor[$field_name])
241
            && $field_settings = $this->get_model()->field_settings_for($field_name)
242
        ) {
243
            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
244
        } else {
245
            return null;
246
        }
247
    }
248
249
250
251
    /**
252
     * @param EE_Base_Class $obj
253
     * @return string
254
     */
255
    public function get_class($obj)
256
    {
257
        return get_class($obj);
258
    }
259
260
261
262
    /**
263
     * Overrides parent because parent expects old models.
264
     * This also doesn't do any validation, and won't work for serialized arrays
265
     *
266
     * @param    string $field_name
267
     * @param    mixed  $field_value
268
     * @param bool      $use_default
269
     * @throws \EE_Error
270
     */
271
    public function set($field_name, $field_value, $use_default = false)
272
    {
273
        // if not using default and nothing has changed, and object has already been setup (has ID),
274
        // then don't do anything
275
        if (
276
            ! $use_default
277
            && $this->_fields[$field_name] === $field_value
278
            && $this->ID()
279
        ) {
280
            return;
281
        }
282
        $model = $this->get_model();
283
        $this->_has_changes = true;
284
        $field_obj = $model->field_settings_for($field_name);
285
        if ($field_obj instanceof EE_Model_Field_Base) {
286
            //			if ( method_exists( $field_obj, 'set_timezone' )) {
287
            if ($field_obj instanceof EE_Datetime_Field) {
288
                $field_obj->set_timezone($this->_timezone);
289
                $field_obj->set_date_format($this->_dt_frmt);
290
                $field_obj->set_time_format($this->_tm_frmt);
291
            }
292
            $holder_of_value = $field_obj->prepare_for_set($field_value);
293
            //should the value be null?
294
            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
295
                $this->_fields[$field_name] = $field_obj->get_default_value();
296
                /**
297
                 * To save having to refactor all the models, if a default value is used for a
298
                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
299
                 * object.  Then let's do a set again to ensure that it becomes a DateTime
300
                 * object.
301
                 *
302
                 * @since 4.6.10+
303
                 */
304
                if (
305
                    $field_obj instanceof EE_Datetime_Field
306
                    && $this->_fields[$field_name] !== null
307
                    && ! $this->_fields[$field_name] instanceof DateTime
308
                ) {
309
                    empty($this->_fields[$field_name])
310
                        ? $this->set($field_name, time())
311
                        : $this->set($field_name, $this->_fields[$field_name]);
312
                }
313
            } else {
314
                $this->_fields[$field_name] = $holder_of_value;
315
            }
316
            //if we're not in the constructor...
317
            //now check if what we set was a primary key
318
            if (
319
                //note: props_n_values_provided_in_constructor is only set at the END of the constructor
320
                $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...
321
                && $field_value
322
                && $field_name === $model->primary_key_name()
323
            ) {
324
                //if so, we want all this object's fields to be filled either with
325
                //what we've explicitly set on this model
326
                //or what we have in the db
327
                // echo "setting primary key!";
328
                $fields_on_model = self::_get_model(get_class($this))->field_settings();
329
                $obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
330
                foreach ($fields_on_model as $field_obj) {
331
                    if ( ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
332
                         && $field_obj->get_name() !== $field_name
333
                    ) {
334
                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
335
                    }
336
                }
337
                //oh this model object has an ID? well make sure its in the entity mapper
338
                $model->add_to_entity_map($this);
339
            }
340
            //let's unset any cache for this field_name from the $_cached_properties property.
341
            $this->_clear_cached_property($field_name);
342
        } else {
343
            throw new EE_Error(sprintf(__("A valid EE_Model_Field_Base could not be found for the given field name: %s",
344
                "event_espresso"), $field_name));
345
        }
346
    }
347
348
349
350
    /**
351
     * This sets the field value on the db column if it exists for the given $column_name or
352
     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
353
     *
354
     * @see EE_message::get_column_value for related documentation on the necessity of this method.
355
     * @param string $field_name  Must be the exact column name.
356
     * @param mixed  $field_value The value to set.
357
     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
358
     * @throws \EE_Error
359
     */
360
    public function set_field_or_extra_meta($field_name, $field_value)
361
    {
362
        if ($this->get_model()->has_field($field_name)) {
363
            $this->set($field_name, $field_value);
364
            return true;
365
        } else {
366
            //ensure this object is saved first so that extra meta can be properly related.
367
            $this->save();
368
            return $this->update_extra_meta($field_name, $field_value);
369
        }
370
    }
371
372
373
374
    /**
375
     * This retrieves the value of the db column set on this class or if that's not present
376
     * it will attempt to retrieve from extra_meta if found.
377
     * Example Usage:
378
     * Via EE_Message child class:
379
     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
380
     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
381
     * also have additional main fields specific to the messenger.  The system accommodates those extra
382
     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
383
     * value for those extra fields dynamically via the EE_message object.
384
     *
385
     * @param  string $field_name expecting the fully qualified field name.
386
     * @return mixed|null  value for the field if found.  null if not found.
387
     * @throws \EE_Error
388
     */
389
    public function get_field_or_extra_meta($field_name)
390
    {
391
        if ($this->get_model()->has_field($field_name)) {
392
            $column_value = $this->get($field_name);
393
        } else {
394
            //This isn't a column in the main table, let's see if it is in the extra meta.
395
            $column_value = $this->get_extra_meta($field_name, true, null);
396
        }
397
        return $column_value;
398
    }
399
400
401
402
    /**
403
     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
404
     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
405
     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
406
     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
407
     *
408
     * @access public
409
     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
410
     * @return void
411
     * @throws \EE_Error
412
     */
413
    public function set_timezone($timezone = '')
414
    {
415
        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
416
        //make sure we clear all cached properties because they won't be relevant now
417
        $this->_clear_cached_properties();
418
        //make sure we update field settings and the date for all EE_Datetime_Fields
419
        $model_fields = $this->get_model()->field_settings(false);
420
        foreach ($model_fields as $field_name => $field_obj) {
421
            if ($field_obj instanceof EE_Datetime_Field) {
422
                $field_obj->set_timezone($this->_timezone);
423
                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
424
                    $this->_fields[$field_name]->setTimezone(new DateTimeZone($this->_timezone));
425
                }
426
            }
427
        }
428
    }
429
430
431
432
    /**
433
     * This just returns whatever is set for the current timezone.
434
     *
435
     * @access public
436
     * @return string timezone string
437
     */
438
    public function get_timezone()
439
    {
440
        return $this->_timezone;
441
    }
442
443
444
445
    /**
446
     * This sets the internal date format to what is sent in to be used as the new default for the class
447
     * internally instead of wp set date format options
448
     *
449
     * @since 4.6
450
     * @param string $format should be a format recognizable by PHP date() functions.
451
     */
452
    public function set_date_format($format)
453
    {
454
        $this->_dt_frmt = $format;
455
        //clear cached_properties because they won't be relevant now.
456
        $this->_clear_cached_properties();
457
    }
458
459
460
461
    /**
462
     * This sets the internal time format string to what is sent in to be used as the new default for the
463
     * class internally instead of wp set time format options.
464
     *
465
     * @since 4.6
466
     * @param string $format should be a format recognizable by PHP date() functions.
467
     */
468
    public function set_time_format($format)
469
    {
470
        $this->_tm_frmt = $format;
471
        //clear cached_properties because they won't be relevant now.
472
        $this->_clear_cached_properties();
473
    }
474
475
476
477
    /**
478
     * This returns the current internal set format for the date and time formats.
479
     *
480
     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
481
     *                             where the first value is the date format and the second value is the time format.
482
     * @return mixed string|array
483
     */
484
    public function get_format($full = true)
485
    {
486
        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
487
    }
488
489
490
491
    /**
492
     * cache
493
     * stores the passed model object on the current model object.
494
     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
495
     *
496
     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
497
     *                                       'Registration' associated with this model object
498
     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
499
     *                                       that could be a payment or a registration)
500
     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
501
     *                                       items which will be stored in an array on this object
502
     * @throws EE_Error
503
     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
504
     *                  related thing, no array)
505
     */
506
    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
507
    {
508
        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
509
        if ( ! $object_to_cache instanceof EE_Base_Class) {
510
            return false;
511
        }
512
        // also get "how" the object is related, or throw an error
513
        if ( ! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
514
            throw new EE_Error(sprintf(__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
515
                $relationName, get_class($this)));
516
        }
517
        // how many things are related ?
518
        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
519
            // if it's a "belongs to" relationship, then there's only one related model object  eg, if this is a registration, there's only 1 attendee for it
520
            // so for these model objects just set it to be cached
521
            $this->_model_relations[$relationName] = $object_to_cache;
522
            $return = true;
523
        } else {
524
            // otherwise, this is the "many" side of a one to many relationship, so we'll add the object to the array of related objects for that type.
525
            // eg: if this is an event, there are many registrations for that event, so we cache the registrations in an array
526
            if ( ! is_array($this->_model_relations[$relationName])) {
527
                // if for some reason, the cached item is a model object, then stick that in the array, otherwise start with an empty array
528
                $this->_model_relations[$relationName] = $this->_model_relations[$relationName] instanceof EE_Base_Class
529
                    ? array($this->_model_relations[$relationName]) : array();
530
            }
531
            // first check for a cache_id which is normally empty
532
            if ( ! empty($cache_id)) {
533
                // if the cache_id exists, then it means we are purposely trying to cache this with a known key that can then be used to retrieve the object later on
534
                $this->_model_relations[$relationName][$cache_id] = $object_to_cache;
535
                $return = $cache_id;
536
            } elseif ($object_to_cache->ID()) {
537
                // OR the cached object originally came from the db, so let's just use it's PK for an ID
538
                $this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
539
                $return = $object_to_cache->ID();
540
            } else {
541
                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
542
                $this->_model_relations[$relationName][] = $object_to_cache;
543
                // move the internal pointer to the end of the array
544
                end($this->_model_relations[$relationName]);
545
                // and grab the key so that we can return it
546
                $return = key($this->_model_relations[$relationName]);
547
            }
548
        }
549
        return $return;
550
    }
551
552
553
554
    /**
555
     * For adding an item to the cached_properties property.
556
     *
557
     * @access protected
558
     * @param string      $fieldname the property item the corresponding value is for.
559
     * @param mixed       $value     The value we are caching.
560
     * @param string|null $cache_type
561
     * @return void
562
     * @throws \EE_Error
563
     */
564
    protected function _set_cached_property($fieldname, $value, $cache_type = null)
565
    {
566
        //first make sure this property exists
567
        $this->get_model()->field_settings_for($fieldname);
568
        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
569
        $this->_cached_properties[$fieldname][$cache_type] = $value;
570
    }
571
572
573
574
    /**
575
     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
576
     * This also SETS the cache if we return the actual property!
577
     *
578
     * @param string $fieldname        the name of the property we're trying to retrieve
579
     * @param bool   $pretty
580
     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
581
     *                                 (in cases where the same property may be used for different outputs
582
     *                                 - i.e. datetime, money etc.)
583
     *                                 It can also accept certain pre-defined "schema" strings
584
     *                                 to define how to output the property.
585
     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
586
     * @return mixed                   whatever the value for the property is we're retrieving
587
     * @throws \EE_Error
588
     */
589
    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
590
    {
591
        //verify the field exists
592
        $model = $this->get_model();
593
        $model->field_settings_for($fieldname);
594
        $cache_type = $pretty ? 'pretty' : 'standard';
595
        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
596
        if (isset($this->_cached_properties[$fieldname][$cache_type])) {
597
            return $this->_cached_properties[$fieldname][$cache_type];
598
        }
599
        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
600
        $this->_set_cached_property($fieldname, $value, $cache_type);
601
        return $value;
602
    }
603
604
605
606
    /**
607
     * If the cache didn't fetch the needed item, this fetches it.
608
     * @param string $fieldname
609
     * @param bool $pretty
610
     * @param string $extra_cache_ref
611
     * @return mixed
612
     */
613
    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
614
    {
615
        $field_obj = $this->get_model()->field_settings_for($fieldname);
616
        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
617
        if ($field_obj instanceof EE_Datetime_Field) {
618
            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
619
        }
620
        if ( ! isset($this->_fields[$fieldname])) {
621
            $this->_fields[$fieldname] = null;
622
        }
623
        $value = $pretty
624
            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$fieldname], $extra_cache_ref)
625
            : $field_obj->prepare_for_get($this->_fields[$fieldname]);
626
        return $value;
627
    }
628
629
630
631
    /**
632
     * set timezone, formats, and output for EE_Datetime_Field objects
633
     *
634
     * @param \EE_Datetime_Field $datetime_field
635
     * @param bool               $pretty
636
     * @param null $date_or_time
637
     * @return void
638
     * @throws \EE_Error
639
     */
640
    protected function _prepare_datetime_field(
641
        EE_Datetime_Field $datetime_field,
642
        $pretty = false,
643
        $date_or_time = null
644
    ) {
645
        $datetime_field->set_timezone($this->_timezone);
646
        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
647
        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
648
        //set the output returned
649
        switch ($date_or_time) {
650
            case 'D' :
651
                $datetime_field->set_date_time_output('date');
652
                break;
653
            case 'T' :
654
                $datetime_field->set_date_time_output('time');
655
                break;
656
            default :
657
                $datetime_field->set_date_time_output();
658
        }
659
    }
660
661
662
663
    /**
664
     * This just takes care of clearing out the cached_properties
665
     *
666
     * @return void
667
     */
668
    protected function _clear_cached_properties()
669
    {
670
        $this->_cached_properties = array();
671
    }
672
673
674
675
    /**
676
     * This just clears out ONE property if it exists in the cache
677
     *
678
     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
679
     * @return void
680
     */
681
    protected function _clear_cached_property($property_name)
682
    {
683
        if (isset($this->_cached_properties[$property_name])) {
684
            unset($this->_cached_properties[$property_name]);
685
        }
686
    }
687
688
689
690
    /**
691
     * Ensures that this related thing is a model object.
692
     *
693
     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
694
     * @param string $model_name   name of the related thing, eg 'Attendee',
695
     * @return EE_Base_Class
696
     * @throws \EE_Error
697
     */
698
    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
699
    {
700
        $other_model_instance = self::_get_model_instance_with_name(
701
            self::_get_model_classname($model_name),
702
            $this->_timezone
703
        );
704
        return $other_model_instance->ensure_is_obj($object_or_id);
705
    }
706
707
708
709
    /**
710
     * Forgets the cached model of the given relation Name. So the next time we request it,
711
     * we will fetch it again from the database. (Handy if you know it's changed somehow).
712
     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
713
     * then only remove that one object from our cached array. Otherwise, clear the entire list
714
     *
715
     * @param string $relationName                         one of the keys in the _model_relations array on the model.
716
     *                                                     Eg 'Registration'
717
     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
718
     *                                                     if you intend to use $clear_all = TRUE, or the relation only
719
     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
720
     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
721
     *                                                     this is HasMany or HABTM.
722
     * @throws EE_Error
723
     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
724
     *                       relation from all
725
     */
726
    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
727
    {
728
        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
729
        $index_in_cache = '';
730
        if ( ! $relationship_to_model) {
731
            throw new EE_Error(
732
                sprintf(
733
                    __("There is no relationship to %s on a %s. Cannot clear that cache", 'event_espresso'),
734
                    $relationName,
735
                    get_class($this)
736
                )
737
            );
738
        }
739
        if ($clear_all) {
740
            $obj_removed = true;
741
            $this->_model_relations[$relationName] = null;
742
        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
743
            $obj_removed = $this->_model_relations[$relationName];
744
            $this->_model_relations[$relationName] = null;
745
        } else {
746
            if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
747
                && $object_to_remove_or_index_into_array->ID()
748
            ) {
749
                $index_in_cache = $object_to_remove_or_index_into_array->ID();
750
                if (is_array($this->_model_relations[$relationName])
751
                    && ! isset($this->_model_relations[$relationName][$index_in_cache])
752
                ) {
753
                    $index_found_at = null;
754
                    //find this object in the array even though it has a different key
755
                    foreach ($this->_model_relations[$relationName] as $index => $obj) {
756
                        if (
757
                            $obj instanceof EE_Base_Class
758
                            && (
759
                                $obj == $object_to_remove_or_index_into_array
760
                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
761
                            )
762
                        ) {
763
                            $index_found_at = $index;
764
                            break;
765
                        }
766
                    }
767
                    if ($index_found_at) {
768
                        $index_in_cache = $index_found_at;
769
                    } else {
770
                        //it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
771
                        //if it wasn't in it to begin with. So we're done
772
                        return $object_to_remove_or_index_into_array;
773
                    }
774
                }
775
            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
776
                //so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
777
                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
778
                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
779
                        $index_in_cache = $index;
780
                    }
781
                }
782
            } else {
783
                $index_in_cache = $object_to_remove_or_index_into_array;
784
            }
785
            //supposedly we've found it. But it could just be that the client code
786
            //provided a bad index/object
787
            if (
788
            isset(
789
                $this->_model_relations[$relationName],
790
                $this->_model_relations[$relationName][$index_in_cache]
791
            )
792
            ) {
793
                $obj_removed = $this->_model_relations[$relationName][$index_in_cache];
794
                unset($this->_model_relations[$relationName][$index_in_cache]);
795
            } else {
796
                //that thing was never cached anyways.
797
                $obj_removed = null;
798
            }
799
        }
800
        return $obj_removed;
801
    }
802
803
804
805
    /**
806
     * update_cache_after_object_save
807
     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
808
     * obtained after being saved to the db
809
     *
810
     * @param string         $relationName       - the type of object that is cached
811
     * @param \EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
812
     * @param string         $current_cache_id   - the ID that was used when originally caching the object
813
     * @return boolean TRUE on success, FALSE on fail
814
     * @throws \EE_Error
815
     */
816
    public function update_cache_after_object_save(
817
        $relationName,
818
        EE_Base_Class $newly_saved_object,
819
        $current_cache_id = ''
820
    ) {
821
        // verify that incoming object is of the correct type
822
        $obj_class = 'EE_' . $relationName;
823
        if ($newly_saved_object instanceof $obj_class) {
824
            /* @type EE_Base_Class $newly_saved_object */
825
            // now get the type of relation
826
            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
827
            // if this is a 1:1 relationship
828
            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
829
                // then just replace the cached object with the newly saved object
830
                $this->_model_relations[$relationName] = $newly_saved_object;
831
                return true;
832
                // or if it's some kind of sordid feral polyamorous relationship...
833
            } elseif (is_array($this->_model_relations[$relationName])
834
                      && isset($this->_model_relations[$relationName][$current_cache_id])
835
            ) {
836
                // then remove the current cached item
837
                unset($this->_model_relations[$relationName][$current_cache_id]);
838
                // and cache the newly saved object using it's new ID
839
                $this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
840
                return true;
841
            }
842
        }
843
        return false;
844
    }
845
846
847
848
    /**
849
     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
850
     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
851
     *
852
     * @param string $relationName
853
     * @return EE_Base_Class
854
     */
855
    public function get_one_from_cache($relationName)
856
    {
857
        $cached_array_or_object = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName]
858
            : null;
859
        if (is_array($cached_array_or_object)) {
860
            return array_shift($cached_array_or_object);
861
        } else {
862
            return $cached_array_or_object;
863
        }
864
    }
865
866
867
868
    /**
869
     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
870
     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
871
     *
872
     * @param string $relationName
873
     * @throws \EE_Error
874
     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
875
     */
876
    public function get_all_from_cache($relationName)
877
    {
878
        $objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : array();
879
        // if the result is not an array, but exists, make it an array
880
        $objects = is_array($objects) ? $objects : array($objects);
881
        //bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
882
        //basically, if this model object was stored in the session, and these cached model objects
883
        //already have IDs, let's make sure they're in their model's entity mapper
884
        //otherwise we will have duplicates next time we call
885
        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
886
        $model = EE_Registry::instance()->load_model($relationName);
887
        foreach ($objects as $model_object) {
888
            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
889
                //ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
890
                if ($model_object->ID()) {
891
                    $model->add_to_entity_map($model_object);
892
                }
893
            } else {
894
                throw new EE_Error(
895
                    sprintf(
896
                        __(
897
                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
898
                            'event_espresso'
899
                        ),
900
                        $relationName,
901
                        gettype($model_object)
902
                    )
903
                );
904
            }
905
        }
906
        return $objects;
907
    }
908
909
910
911
    /**
912
     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
913
     * matching the given query conditions.
914
     *
915
     * @param null  $field_to_order_by  What field is being used as the reference point.
916
     * @param int   $limit              How many objects to return.
917
     * @param array $query_params       Any additional conditions on the query.
918
     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
919
     *                                  you can indicate just the columns you want returned
920
     * @return array|EE_Base_Class[]
921
     * @throws \EE_Error
922
     */
923 View Code Duplication
    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
924
    {
925
        $model = $this->get_model();
926
        $field = empty($field_to_order_by) && $model->has_primary_key_field()
927
            ? $model->get_primary_key_field()->get_name()
928
            : $field_to_order_by;
929
        $current_value = ! empty($field) ? $this->get($field) : null;
930
        if (empty($field) || empty($current_value)) {
931
            return array();
932
        }
933
        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
934
    }
935
936
937
938
    /**
939
     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
940
     * matching the given query conditions.
941
     *
942
     * @param null  $field_to_order_by  What field is being used as the reference point.
943
     * @param int   $limit              How many objects to return.
944
     * @param array $query_params       Any additional conditions on the query.
945
     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
946
     *                                  you can indicate just the columns you want returned
947
     * @return array|EE_Base_Class[]
948
     * @throws \EE_Error
949
     */
950 View Code Duplication
    public function previous_x(
951
        $field_to_order_by = null,
952
        $limit = 1,
953
        $query_params = array(),
954
        $columns_to_select = null
955
    ) {
956
        $model = $this->get_model();
957
        $field = empty($field_to_order_by) && $model->has_primary_key_field()
958
            ? $model->get_primary_key_field()->get_name()
959
            : $field_to_order_by;
960
        $current_value = ! empty($field) ? $this->get($field) : null;
961
        if (empty($field) || empty($current_value)) {
962
            return array();
963
        }
964
        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
965
    }
966
967
968
969
    /**
970
     * Returns the next EE_Base_Class object in sequence from this object as found in the database
971
     * matching the given query conditions.
972
     *
973
     * @param null  $field_to_order_by  What field is being used as the reference point.
974
     * @param array $query_params       Any additional conditions on the query.
975
     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
976
     *                                  you can indicate just the columns you want returned
977
     * @return array|EE_Base_Class
978
     * @throws \EE_Error
979
     */
980 View Code Duplication
    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
981
    {
982
        $model = $this->get_model();
983
        $field = empty($field_to_order_by) && $model->has_primary_key_field()
984
            ? $model->get_primary_key_field()->get_name()
985
            : $field_to_order_by;
986
        $current_value = ! empty($field) ? $this->get($field) : null;
987
        if (empty($field) || empty($current_value)) {
988
            return array();
989
        }
990
        return $model->next($current_value, $field, $query_params, $columns_to_select);
991
    }
992
993
994
995
    /**
996
     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
997
     * matching the given query conditions.
998
     *
999
     * @param null  $field_to_order_by  What field is being used as the reference point.
1000
     * @param array $query_params       Any additional conditions on the query.
1001
     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1002
     *                                  you can indicate just the column you want returned
1003
     * @return array|EE_Base_Class
1004
     * @throws \EE_Error
1005
     */
1006 View Code Duplication
    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1007
    {
1008
        $model = $this->get_model();
1009
        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1010
            ? $model->get_primary_key_field()->get_name()
1011
            : $field_to_order_by;
1012
        $current_value = ! empty($field) ? $this->get($field) : null;
1013
        if (empty($field) || empty($current_value)) {
1014
            return array();
1015
        }
1016
        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1017
    }
1018
1019
1020
1021
    /**
1022
     * Overrides parent because parent expects old models.
1023
     * This also doesn't do any validation, and won't work for serialized arrays
1024
     *
1025
     * @param string $field_name
1026
     * @param mixed  $field_value_from_db
1027
     * @throws \EE_Error
1028
     */
1029
    public function set_from_db($field_name, $field_value_from_db)
1030
    {
1031
        $field_obj = $this->get_model()->field_settings_for($field_name);
1032
        if ($field_obj instanceof EE_Model_Field_Base) {
1033
            //you would think the DB has no NULLs for non-null label fields right? wrong!
1034
            //eg, a CPT model object could have an entry in the posts table, but no
1035
            //entry in the meta table. Meaning that all its columns in the meta table
1036
            //are null! yikes! so when we find one like that, use defaults for its meta columns
1037
            if ($field_value_from_db === null) {
1038
                if ($field_obj->is_nullable()) {
1039
                    //if the field allows nulls, then let it be null
1040
                    $field_value = null;
1041
                } else {
1042
                    $field_value = $field_obj->get_default_value();
1043
                }
1044
            } else {
1045
                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1046
            }
1047
            $this->_fields[$field_name] = $field_value;
1048
            $this->_clear_cached_property($field_name);
1049
        }
1050
    }
1051
1052
1053
1054
    /**
1055
     * verifies that the specified field is of the correct type
1056
     *
1057
     * @param string $field_name
1058
     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1059
     *                                (in cases where the same property may be used for different outputs
1060
     *                                - i.e. datetime, money etc.)
1061
     * @return mixed
1062
     * @throws \EE_Error
1063
     */
1064
    public function get($field_name, $extra_cache_ref = null)
1065
    {
1066
        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1067
    }
1068
1069
1070
1071
    /**
1072
     * This method simply returns the RAW unprocessed value for the given property in this class
1073
     *
1074
     * @param  string $field_name A valid fieldname
1075
     * @return mixed              Whatever the raw value stored on the property is.
1076
     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1077
     */
1078
    public function get_raw($field_name)
1079
    {
1080
        $field_settings = $this->get_model()->field_settings_for($field_name);
1081
        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
1082
            ? $this->_fields[$field_name]->format('U')
1083
            : $this->_fields[$field_name];
1084
    }
1085
1086
1087
1088
    /**
1089
     * This is used to return the internal DateTime object used for a field that is a
1090
     * EE_Datetime_Field.
1091
     *
1092
     * @param string $field_name               The field name retrieving the DateTime object.
1093
     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1094
     * @throws \EE_Error
1095
     *                                         an error is set and false returned.  If the field IS an
1096
     *                                         EE_Datetime_Field and but the field value is null, then
1097
     *                                         just null is returned (because that indicates that likely
1098
     *                                         this field is nullable).
1099
     */
1100
    public function get_DateTime_object($field_name)
1101
    {
1102
        $field_settings = $this->get_model()->field_settings_for($field_name);
1103 View Code Duplication
        if ( ! $field_settings instanceof EE_Datetime_Field) {
1104
            EE_Error::add_error(
1105
                sprintf(
1106
                    __(
1107
                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1108
                        'event_espresso'
1109
                    ),
1110
                    $field_name
1111
                ),
1112
                __FILE__,
1113
                __FUNCTION__,
1114
                __LINE__
1115
            );
1116
            return false;
1117
        }
1118
        return $this->_fields[$field_name];
1119
    }
1120
1121
1122
1123
    /**
1124
     * Gets a Money object for the specified field. Please note that this should only be
1125
     * used for fields corresponding to EE_Money_Fields, and it will always return a money object,
1126
     * or else it will throw an exception.
1127
     *
1128
     * @param $field_name
1129
     * @return mixed
1130
     * @throws InvalidEntityException
1131
     * @throws EE_Error
1132
     */
1133
    public function getMoneyObject($field_name)
1134
    {
1135
        $field = $this->get_model()->field_settings_for($field_name);
1136
        $value = isset($this->_fields[$field_name]) ? $this->_fields[$field_name] : null;
1137
        if (! $field instanceof EE_Money_Field
1138
            || ! $value instanceof Money) {
1139
            throw new InvalidEntityException(
1140
                get_class($value),
1141
                'Money',
1142
                sprintf(
1143
                    esc_html__(
1144
                        // @codingStandardsIgnoreStart
1145
                        'Tried to retrieve money value from %1$s with ID %2$s from field %3$s but no money object present.',
1146
                        // @codingStandardsIgnoreEnd
1147
                        'event_espresso'
1148
                    ),
1149
                    get_class($this),
1150
                    $this->ID(),
1151
                    $field_name
1152
                )
1153
            );
1154
        }
1155
        return $value;
1156
    }
1157
1158
1159
1160
    /**
1161
     * To be used in template to immediately echo out the value, and format it for output.
1162
     * Eg, should call stripslashes and whatnot before echoing
1163
     *
1164
     * @param string $field_name      the name of the field as it appears in the DB
1165
     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1166
     *                                (in cases where the same property may be used for different outputs
1167
     *                                - i.e. datetime, money etc.)
1168
     * @return void
1169
     * @throws \EE_Error
1170
     */
1171
    public function e($field_name, $extra_cache_ref = null)
1172
    {
1173
        echo $this->get_pretty($field_name, $extra_cache_ref);
1174
    }
1175
1176
1177
1178
    /**
1179
     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1180
     * can be easily used as the value of form input.
1181
     *
1182
     * @param string $field_name
1183
     * @return void
1184
     * @throws \EE_Error
1185
     */
1186
    public function f($field_name)
1187
    {
1188
        $this->e($field_name, 'form_input');
1189
    }
1190
1191
1192
1193
    /**
1194
     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1195
     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1196
     * to see what options are available.
1197
     * @param string $field_name
1198
     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1199
     *                                (in cases where the same property may be used for different outputs
1200
     *                                - i.e. datetime, money etc.)
1201
     * @return mixed
1202
     * @throws \EE_Error
1203
     */
1204
    public function get_pretty($field_name, $extra_cache_ref = null)
1205
    {
1206
        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1207
    }
1208
1209
1210
1211
    /**
1212
     * This simply returns the datetime for the given field name
1213
     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1214
     * (and the equivalent e_date, e_time, e_datetime).
1215
     *
1216
     * @access   protected
1217
     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1218
     * @param string   $dt_frmt      valid datetime format used for date
1219
     *                               (if '' then we just use the default on the field,
1220
     *                               if NULL we use the last-used format)
1221
     * @param string   $tm_frmt      Same as above except this is for time format
1222
     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1223
     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1224
     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1225
     *                               if field is not a valid dtt field, or void if echoing
1226
     * @throws \EE_Error
1227
     */
1228
    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1229
    {
1230
        // clear cached property
1231
        $this->_clear_cached_property($field_name);
1232
        //reset format properties because they are used in get()
1233
        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1234
        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1235
        if ($echo) {
1236
            $this->e($field_name, $date_or_time);
1237
            return '';
1238
        }
1239
        return $this->get($field_name, $date_or_time);
1240
    }
1241
1242
1243
1244
    /**
1245
     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1246
     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1247
     * other echoes the pretty value for dtt)
1248
     *
1249
     * @param  string $field_name name of model object datetime field holding the value
1250
     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1251
     * @return string            datetime value formatted
1252
     * @throws \EE_Error
1253
     */
1254
    public function get_date($field_name, $format = '')
1255
    {
1256
        return $this->_get_datetime($field_name, $format, null, 'D');
1257
    }
1258
1259
1260
1261
    /**
1262
     * @param      $field_name
1263
     * @param string $format
1264
     * @throws \EE_Error
1265
     */
1266
    public function e_date($field_name, $format = '')
1267
    {
1268
        $this->_get_datetime($field_name, $format, null, 'D', true);
1269
    }
1270
1271
1272
1273
    /**
1274
     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1275
     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1276
     * other echoes the pretty value for dtt)
1277
     *
1278
     * @param  string $field_name name of model object datetime field holding the value
1279
     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1280
     * @return string             datetime value formatted
1281
     * @throws \EE_Error
1282
     */
1283
    public function get_time($field_name, $format = '')
1284
    {
1285
        return $this->_get_datetime($field_name, null, $format, 'T');
1286
    }
1287
1288
1289
1290
    /**
1291
     * @param      $field_name
1292
     * @param string $format
1293
     * @throws \EE_Error
1294
     */
1295
    public function e_time($field_name, $format = '')
1296
    {
1297
        $this->_get_datetime($field_name, null, $format, 'T', true);
1298
    }
1299
1300
1301
1302
    /**
1303
     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1304
     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1305
     * other echoes the pretty value for dtt)
1306
     *
1307
     * @param  string $field_name name of model object datetime field holding the value
1308
     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1309
     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1310
     * @return string             datetime value formatted
1311
     * @throws \EE_Error
1312
     */
1313
    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1314
    {
1315
        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1316
    }
1317
1318
1319
1320
    /**
1321
     * @param string $field_name
1322
     * @param string $dt_frmt
1323
     * @param string $tm_frmt
1324
     * @throws \EE_Error
1325
     */
1326
    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1327
    {
1328
        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1329
    }
1330
1331
1332
1333
    /**
1334
     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1335
     *
1336
     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1337
     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1338
     *                           on the object will be used.
1339
     * @return string Date and time string in set locale or false if no field exists for the given
1340
     * @throws \EE_Error
1341
     *                           field name.
1342
     */
1343
    public function get_i18n_datetime($field_name, $format = '')
1344
    {
1345
        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1346
        return date_i18n(
1347
            $format,
1348
            EEH_DTT_Helper::get_timestamp_with_offset($this->get_raw($field_name), $this->_timezone)
1349
        );
1350
    }
1351
1352
1353
1354
    /**
1355
     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1356
     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1357
     * thrown.
1358
     *
1359
     * @param  string $field_name The field name being checked
1360
     * @throws EE_Error
1361
     * @return EE_Datetime_Field
1362
     */
1363
    protected function _get_dtt_field_settings($field_name)
1364
    {
1365
        $field = $this->get_model()->field_settings_for($field_name);
1366
        //check if field is dtt
1367
        if ($field instanceof EE_Datetime_Field) {
1368
            return $field;
1369
        } else {
1370
            throw new EE_Error(sprintf(__('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',
1371
                'event_espresso'), $field_name, self::_get_model_classname(get_class($this))));
1372
        }
1373
    }
1374
1375
1376
1377
1378
    /**
1379
     * NOTE ABOUT BELOW:
1380
     * These convenience date and time setters are for setting date and time independently.  In other words you might
1381
     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1382
     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1383
     * method and make sure you send the entire datetime value for setting.
1384
     */
1385
    /**
1386
     * sets the time on a datetime property
1387
     *
1388
     * @access protected
1389
     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1390
     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1391
     * @throws \EE_Error
1392
     */
1393
    protected function _set_time_for($time, $fieldname)
1394
    {
1395
        $this->_set_date_time('T', $time, $fieldname);
1396
    }
1397
1398
1399
1400
    /**
1401
     * sets the date on a datetime property
1402
     *
1403
     * @access protected
1404
     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1405
     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1406
     * @throws \EE_Error
1407
     */
1408
    protected function _set_date_for($date, $fieldname)
1409
    {
1410
        $this->_set_date_time('D', $date, $fieldname);
1411
    }
1412
1413
1414
1415
    /**
1416
     * This takes care of setting a date or time independently on a given model object property. This method also
1417
     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1418
     *
1419
     * @access protected
1420
     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1421
     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1422
     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1423
     *                                        EE_Datetime_Field property)
1424
     * @throws \EE_Error
1425
     */
1426
    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1427
    {
1428
        $field = $this->_get_dtt_field_settings($fieldname);
1429
        $field->set_timezone($this->_timezone);
1430
        $field->set_date_format($this->_dt_frmt);
1431
        $field->set_time_format($this->_tm_frmt);
1432
        switch ($what) {
1433
            case 'T' :
1434
                $this->_fields[$fieldname] = $field->prepare_for_set_with_new_time(
1435
                    $datetime_value,
1436
                    $this->_fields[$fieldname]
1437
                );
1438
                break;
1439
            case 'D' :
1440
                $this->_fields[$fieldname] = $field->prepare_for_set_with_new_date(
1441
                    $datetime_value,
1442
                    $this->_fields[$fieldname]
1443
                );
1444
                break;
1445
            case 'B' :
1446
                $this->_fields[$fieldname] = $field->prepare_for_set($datetime_value);
1447
                break;
1448
        }
1449
        $this->_clear_cached_property($fieldname);
1450
    }
1451
1452
1453
1454
    /**
1455
     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1456
     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1457
     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1458
     * that could lead to some unexpected results!
1459
     *
1460
     * @access public
1461
     * @param string               $field_name This is the name of the field on the object that contains the date/time
1462
     *                                         value being returned.
1463
     * @param string               $callback   must match a valid method in this class (defaults to get_datetime)
1464
     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1465
     * @param string               $prepend    You can include something to prepend on the timestamp
1466
     * @param string               $append     You can include something to append on the timestamp
1467
     * @throws EE_Error
1468
     * @return string timestamp
1469
     */
1470
    public function display_in_my_timezone(
1471
        $field_name,
1472
        $callback = 'get_datetime',
1473
        $args = null,
1474
        $prepend = '',
1475
        $append = ''
1476
    ) {
1477
        $timezone = EEH_DTT_Helper::get_timezone();
1478
        if ($timezone === $this->_timezone) {
1479
            return '';
1480
        }
1481
        $original_timezone = $this->_timezone;
1482
        $this->set_timezone($timezone);
1483
        $fn = (array)$field_name;
1484
        $args = array_merge($fn, (array)$args);
1485 View Code Duplication
        if ( ! method_exists($this, $callback)) {
1486
            throw new EE_Error(
1487
                sprintf(
1488
                    __(
1489
                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1490
                        'event_espresso'
1491
                    ),
1492
                    $callback
1493
                )
1494
            );
1495
        }
1496
        $args = (array)$args;
1497
        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1498
        $this->set_timezone($original_timezone);
1499
        return $return;
1500
    }
1501
1502
1503
1504
    /**
1505
     * Deletes this model object.
1506
     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1507
     * override
1508
     * `EE_Base_Class::_delete` NOT this class.
1509
     *
1510
     * @return boolean | int
1511
     * @throws \EE_Error
1512
     */
1513
    public function delete()
1514
    {
1515
        /**
1516
         * Called just before the `EE_Base_Class::_delete` method call.
1517
         * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1518
         * should be aware that `_delete` may not always result in a permanent delete.  For example, `EE_Soft_Delete_Base_Class::_delete`
1519
         * soft deletes (trash) the object and does not permanently delete it.
1520
         *
1521
         * @param EE_Base_Class $model_object about to be 'deleted'
1522
         */
1523
        do_action('AHEE__EE_Base_Class__delete__before', $this);
1524
        $result = $this->_delete();
1525
        /**
1526
         * Called just after the `EE_Base_Class::_delete` method call.
1527
         * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1528
         * should be aware that `_delete` may not always result in a permanent delete.  For example `EE_Soft_Base_Class::_delete`
1529
         * soft deletes (trash) the object and does not permanently delete it.
1530
         *
1531
         * @param EE_Base_Class $model_object that was just 'deleted'
1532
         * @param boolean       $result
1533
         */
1534
        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1535
        return $result;
1536
    }
1537
1538
1539
1540
    /**
1541
     * Calls the specific delete method for the instantiated class.
1542
     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1543
     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1544
     * `EE_Base_Class::delete`
1545
     *
1546
     * @return bool|int
1547
     * @throws \EE_Error
1548
     */
1549
    protected function _delete()
1550
    {
1551
        return $this->delete_permanently();
1552
    }
1553
1554
1555
1556
    /**
1557
     * Deletes this model object permanently from db (but keep in mind related models my block the delete and return an
1558
     * error)
1559
     *
1560
     * @return bool | int
1561
     * @throws \EE_Error
1562
     */
1563
    public function delete_permanently()
1564
    {
1565
        /**
1566
         * Called just before HARD deleting a model object
1567
         *
1568
         * @param EE_Base_Class $model_object about to be 'deleted'
1569
         */
1570
        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1571
        $model = $this->get_model();
1572
        $result = $model->delete_permanently_by_ID($this->ID());
1573
        $this->refresh_cache_of_related_objects();
1574
        /**
1575
         * Called just after HARD deleting a model object
1576
         *
1577
         * @param EE_Base_Class $model_object that was just 'deleted'
1578
         * @param boolean       $result
1579
         */
1580
        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1581
        return $result;
1582
    }
1583
1584
1585
1586
    /**
1587
     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1588
     * related model objects
1589
     *
1590
     * @throws \EE_Error
1591
     */
1592
    public function refresh_cache_of_related_objects()
1593
    {
1594
        $model = $this->get_model();
1595
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1596
            if ( ! empty($this->_model_relations[$relation_name])) {
1597
                $related_objects = $this->_model_relations[$relation_name];
1598
                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1599
                    //this relation only stores a single model object, not an array
1600
                    //but let's make it consistent
1601
                    $related_objects = array($related_objects);
1602
                }
1603
                foreach ($related_objects as $related_object) {
1604
                    //only refresh their cache if they're in memory
1605
                    if ($related_object instanceof EE_Base_Class) {
1606
                        $related_object->clear_cache($model->get_this_model_name(), $this);
1607
                    }
1608
                }
1609
            }
1610
        }
1611
    }
1612
1613
1614
1615
    /**
1616
     *        Saves this object to the database. An array may be supplied to set some values on this
1617
     * object just before saving.
1618
     *
1619
     * @access public
1620
     * @param array $set_cols_n_values keys are field names, values are their new values,
1621
     *                                 if provided during the save() method (often client code will change the fields'
1622
     *                                 values before calling save)
1623
     * @throws \EE_Error
1624
     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1625
     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1626
     */
1627
    public function save($set_cols_n_values = array())
1628
    {
1629
        $model = $this->get_model();
1630
        /**
1631
         * Filters the fields we're about to save on the model object
1632
         *
1633
         * @param array         $set_cols_n_values
1634
         * @param EE_Base_Class $model_object
1635
         */
1636
        $set_cols_n_values = (array)apply_filters('FHEE__EE_Base_Class__save__set_cols_n_values', $set_cols_n_values,
1637
            $this);
1638
        //set attributes as provided in $set_cols_n_values
1639
        foreach ($set_cols_n_values as $column => $value) {
1640
            $this->set($column, $value);
1641
        }
1642
        // no changes ? then don't do anything
1643
        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1644
            return 0;
1645
        }
1646
        /**
1647
         * Saving a model object.
1648
         * Before we perform a save, this action is fired.
1649
         *
1650
         * @param EE_Base_Class $model_object the model object about to be saved.
1651
         */
1652
        do_action('AHEE__EE_Base_Class__save__begin', $this);
1653
        if ( ! $this->allow_persist()) {
1654
            return 0;
1655
        }
1656
        //now get current attribute values
1657
        $save_cols_n_values = $this->_fields;
1658
        //if the object already has an ID, update it. Otherwise, insert it
1659
        //also: change the assumption about values passed to the model NOT being prepare dby the model object. They have been
1660
        $old_assumption_concerning_value_preparation = $model
1661
                                                            ->get_assumption_concerning_values_already_prepared_by_model_object();
1662
        $model->assume_values_already_prepared_by_model_object(true);
1663
        //does this model have an autoincrement PK?
1664
        if ($model->has_primary_key_field()) {
1665
            if ($model->get_primary_key_field()->is_auto_increment()) {
1666
                //ok check if it's set, if so: update; if not, insert
1667
                if ( ! empty($save_cols_n_values[$model->primary_key_name()])) {
1668
                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1669
                } else {
1670
                    unset($save_cols_n_values[$model->primary_key_name()]);
1671
                    $results = $model->insert($save_cols_n_values);
1672
                    if ($results) {
1673
                        //if successful, set the primary key
1674
                        //but don't use the normal SET method, because it will check if
1675
                        //an item with the same ID exists in the mapper & db, then
1676
                        //will find it in the db (because we just added it) and THAT object
1677
                        //will get added to the mapper before we can add this one!
1678
                        //but if we just avoid using the SET method, all that headache can be avoided
1679
                        $pk_field_name = $model->primary_key_name();
1680
                        $this->_fields[$pk_field_name] = $results;
1681
                        $this->_clear_cached_property($pk_field_name);
1682
                        $model->add_to_entity_map($this);
1683
                        $this->_update_cached_related_model_objs_fks();
1684
                    }
1685
                }
1686
            } else {//PK is NOT auto-increment
1687
                //so check if one like it already exists in the db
1688
                if ($model->exists_by_ID($this->ID())) {
1689
                    if (WP_DEBUG && ! $this->in_entity_map()) {
1690
                        throw new EE_Error(
1691
                            sprintf(
1692
                                __('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',
1693
                                    'event_espresso'),
1694
                                get_class($this),
1695
                                get_class($model) . '::instance()->add_to_entity_map()',
1696
                                get_class($model) . '::instance()->get_one_by_ID()',
1697
                                '<br />'
1698
                            )
1699
                        );
1700
                    }
1701
                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1702
                } else {
1703
                    $results = $model->insert($save_cols_n_values);
1704
                    $this->_update_cached_related_model_objs_fks();
1705
                }
1706
            }
1707
        } else {//there is NO primary key
1708
            $already_in_db = false;
1709
            foreach ($model->unique_indexes() as $index) {
1710
                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1711
                if ($model->exists(array($uniqueness_where_params))) {
1712
                    $already_in_db = true;
1713
                }
1714
            }
1715
            if ($already_in_db) {
1716
                $combined_pk_fields_n_values = array_intersect_key($save_cols_n_values,
1717
                    $model->get_combined_primary_key_fields());
1718
                $results = $model->update($save_cols_n_values, $combined_pk_fields_n_values);
1719
            } else {
1720
                $results = $model->insert($save_cols_n_values);
1721
            }
1722
        }
1723
        //restore the old assumption about values being prepared by the model object
1724
        $model
1725
             ->assume_values_already_prepared_by_model_object($old_assumption_concerning_value_preparation);
1726
        /**
1727
         * After saving the model object this action is called
1728
         *
1729
         * @param EE_Base_Class $model_object which was just saved
1730
         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1731
         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1732
         */
1733
        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1734
        $this->_has_changes = false;
1735
        return $results;
1736
    }
1737
1738
1739
1740
    /**
1741
     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1742
     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1743
     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1744
     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1745
     * 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
1746
     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1747
     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1748
     *
1749
     * @return void
1750
     * @throws \EE_Error
1751
     */
1752
    protected function _update_cached_related_model_objs_fks()
1753
    {
1754
        $model = $this->get_model();
1755
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1756
            if ($relation_obj instanceof EE_Has_Many_Relation) {
1757
                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1758
                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1759
                        $model->get_this_model_name()
1760
                    );
1761
                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1762
                    if ($related_model_obj_in_cache->ID()) {
1763
                        $related_model_obj_in_cache->save();
1764
                    }
1765
                }
1766
            }
1767
        }
1768
    }
1769
1770
1771
1772
    /**
1773
     * Saves this model object and its NEW cached relations to the database.
1774
     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1775
     * In order for that to work, we would need to mark model objects as dirty/clean...
1776
     * because otherwise, there's a potential for infinite looping of saving
1777
     * Saves the cached related model objects, and ensures the relation between them
1778
     * and this object and properly setup
1779
     *
1780
     * @return int ID of new model object on save; 0 on failure+
1781
     * @throws \EE_Error
1782
     */
1783
    public function save_new_cached_related_model_objs()
1784
    {
1785
        //make sure this has been saved
1786
        if ( ! $this->ID()) {
1787
            $id = $this->save();
1788
        } else {
1789
            $id = $this->ID();
1790
        }
1791
        //now save all the NEW cached model objects  (ie they don't exist in the DB)
1792
        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1793
            if ($this->_model_relations[$relationName]) {
1794
                //is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1795
                //or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1796
                if ($relationObj instanceof EE_Belongs_To_Relation) {
1797
                    //add a relation to that relation type (which saves the appropriate thing in the process)
1798
                    //but ONLY if it DOES NOT exist in the DB
1799
                    /* @var $related_model_obj EE_Base_Class */
1800
                    $related_model_obj = $this->_model_relations[$relationName];
1801
                    //					if( ! $related_model_obj->ID()){
1802
                    $this->_add_relation_to($related_model_obj, $relationName);
1803
                    $related_model_obj->save_new_cached_related_model_objs();
1804
                    //					}
1805
                } else {
1806
                    foreach ($this->_model_relations[$relationName] as $related_model_obj) {
1807
                        //add a relation to that relation type (which saves the appropriate thing in the process)
1808
                        //but ONLY if it DOES NOT exist in the DB
1809
                        //						if( ! $related_model_obj->ID()){
1810
                        $this->_add_relation_to($related_model_obj, $relationName);
1811
                        $related_model_obj->save_new_cached_related_model_objs();
1812
                        //						}
1813
                    }
1814
                }
1815
            }
1816
        }
1817
        return $id;
1818
    }
1819
1820
1821
1822
    /**
1823
     * for getting a model while instantiated.
1824
     *
1825
     * @return \EEM_Base | \EEM_CPT_Base
1826
     */
1827
    public function get_model()
1828
    {
1829
        if( ! $this->_model){
1830
            $modelName = self::_get_model_classname(get_class($this));
1831
            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
1832
        } else {
1833
            $this->_model->set_timezone($this->_timezone);
1834
        }
1835
1836
        return $this->_model;
1837
    }
1838
1839
1840
1841
    /**
1842
     * @param $props_n_values
1843
     * @param $classname
1844
     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
1845
     * @throws \EE_Error
1846
     */
1847
    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
1848
    {
1849
        //TODO: will not work for Term_Relationships because they have no PK!
1850
        $primary_id_ref = self::_get_primary_key_name($classname);
1851
        if (array_key_exists($primary_id_ref, $props_n_values) && ! empty($props_n_values[$primary_id_ref])) {
1852
            $id = $props_n_values[$primary_id_ref];
1853
            return self::_get_model($classname)->get_from_entity_map($id);
1854
        }
1855
        return false;
1856
    }
1857
1858
1859
1860
    /**
1861
     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
1862
     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
1863
     * 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
1864
     * we return false.
1865
     *
1866
     * @param  array  $props_n_values   incoming array of properties and their values
1867
     * @param  string $classname        the classname of the child class
1868
     * @param null    $timezone
1869
     * @param array   $date_formats     incoming date_formats in an array where the first value is the
1870
     *                                  date_format and the second value is the time format
1871
     * @return mixed (EE_Base_Class|bool)
1872
     * @throws \EE_Error
1873
     */
1874
    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
1875
    {
1876
        $existing = null;
1877
        $model = self::_get_model($classname, $timezone);
1878
        if ($model->has_primary_key_field()) {
1879
            $primary_id_ref = self::_get_primary_key_name($classname);
1880
            if (array_key_exists($primary_id_ref, $props_n_values)
1881
                && ! empty($props_n_values[$primary_id_ref])
1882
            ) {
1883
                $existing = $model->get_one_by_ID(
1884
                    $props_n_values[$primary_id_ref]
1885
                );
1886
            }
1887
        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
1888
            //no primary key on this model, but there's still a matching item in the DB
1889
            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
1890
                self::_get_model($classname, $timezone)->get_index_primary_key_string($props_n_values)
1891
            );
1892
        }
1893
        if ($existing) {
1894
            //set date formats if present before setting values
1895
            if ( ! empty($date_formats) && is_array($date_formats)) {
1896
                $existing->set_date_format($date_formats[0]);
1897
                $existing->set_time_format($date_formats[1]);
1898
            } else {
1899
                //set default formats for date and time
1900
                $existing->set_date_format(get_option('date_format'));
1901
                $existing->set_time_format(get_option('time_format'));
1902
            }
1903
            foreach ($props_n_values as $property => $field_value) {
1904
                $existing->set($property, $field_value);
1905
            }
1906
            return $existing;
1907
        } else {
1908
            return false;
1909
        }
1910
    }
1911
1912
1913
1914
    /**
1915
     * Gets the EEM_*_Model for this class
1916
     *
1917
     * @access public now, as this is more convenient
1918
     * @param      $classname
1919
     * @param null $timezone
1920
     * @throws EE_Error
1921
     * @return EEM_Base
1922
     */
1923
    protected static function _get_model($classname, $timezone = null)
1924
    {
1925
        //find model for this class
1926
        if ( ! $classname) {
1927
            throw new EE_Error(
1928
                sprintf(
1929
                    __(
1930
                        "What were you thinking calling _get_model(%s)?? You need to specify the class name",
1931
                        "event_espresso"
1932
                    ),
1933
                    $classname
1934
                )
1935
            );
1936
        }
1937
        $modelName = self::_get_model_classname($classname);
1938
        return self::_get_model_instance_with_name($modelName, $timezone);
1939
    }
1940
1941
1942
1943
    /**
1944
     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
1945
     *
1946
     * @param string $model_classname
1947
     * @param null   $timezone
1948
     * @return EEM_Base
1949
     */
1950
    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
1951
    {
1952
        $model_classname = str_replace('EEM_', '', $model_classname);
1953
        $model = EE_Registry::instance()->load_model($model_classname);
1954
        $model->set_timezone($timezone);
1955
        return $model;
1956
    }
1957
1958
1959
1960
    /**
1961
     * If a model name is provided (eg Registration), gets the model classname for that model.
1962
     * Also works if a model class's classname is provided (eg EE_Registration).
1963
     *
1964
     * @param null $model_name
1965
     * @return string like EEM_Attendee
1966
     */
1967
    private static function _get_model_classname($model_name = null)
1968
    {
1969
        if (strpos($model_name, "EE_") === 0) {
1970
            $model_classname = str_replace("EE_", "EEM_", $model_name);
1971
        } else {
1972
            $model_classname = "EEM_" . $model_name;
1973
        }
1974
        return $model_classname;
1975
    }
1976
1977
1978
1979
    /**
1980
     * returns the name of the primary key attribute
1981
     *
1982
     * @param null $classname
1983
     * @throws EE_Error
1984
     * @return string
1985
     */
1986
    protected static function _get_primary_key_name($classname = null)
1987
    {
1988
        if ( ! $classname) {
1989
            throw new EE_Error(
1990
                sprintf(
1991
                    __("What were you thinking calling _get_primary_key_name(%s)", "event_espresso"),
1992
                    $classname
1993
                )
1994
            );
1995
        }
1996
        return self::_get_model($classname)->get_primary_key_field()->get_name();
1997
    }
1998
1999
2000
2001
    /**
2002
     * Gets the value of the primary key.
2003
     * If the object hasn't yet been saved, it should be whatever the model field's default was
2004
     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2005
     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2006
     *
2007
     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2008
     * @throws \EE_Error
2009
     */
2010
    public function ID()
2011
    {
2012
        $model = $this->get_model();
2013
        //now that we know the name of the variable, use a variable variable to get its value and return its
2014
        if ($model->has_primary_key_field()) {
2015
            return $this->_fields[$model->primary_key_name()];
2016
        } else {
2017
            return $model->get_index_primary_key_string($this->_fields);
2018
        }
2019
    }
2020
2021
2022
2023
    /**
2024
     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2025
     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2026
     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2027
     *
2028
     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2029
     * @param string $relationName                     eg 'Events','Question',etc.
2030
     *                                                 an attendee to a group, you also want to specify which role they
2031
     *                                                 will have in that group. So you would use this parameter to
2032
     *                                                 specify array('role-column-name'=>'role-id')
2033
     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2034
     *                                                 allow you to further constrict the relation to being added.
2035
     *                                                 However, keep in mind that the columns (keys) given must match a
2036
     *                                                 column on the JOIN table and currently only the HABTM models
2037
     *                                                 accept these additional conditions.  Also remember that if an
2038
     *                                                 exact match isn't found for these extra cols/val pairs, then a
2039
     *                                                 NEW row is created in the join table.
2040
     * @param null   $cache_id
2041
     * @throws EE_Error
2042
     * @return EE_Base_Class the object the relation was added to
2043
     */
2044
    public function _add_relation_to(
2045
        $otherObjectModelObjectOrID,
2046
        $relationName,
2047
        $extra_join_model_fields_n_values = array(),
2048
        $cache_id = null
2049
    ) {
2050
        $model = $this->get_model();
2051
        //if this thing exists in the DB, save the relation to the DB
2052
        if ($this->ID()) {
2053
            $otherObject = $model
2054
                                ->add_relationship_to($this, $otherObjectModelObjectOrID, $relationName,
2055
                                    $extra_join_model_fields_n_values);
2056
            //clear cache so future get_many_related and get_first_related() return new results.
2057
            $this->clear_cache($relationName, $otherObject, true);
2058
            if ($otherObject instanceof EE_Base_Class) {
2059
                $otherObject->clear_cache($model->get_this_model_name(), $this);
2060
            }
2061 View Code Duplication
        } else {
2062
            //this thing doesn't exist in the DB,  so just cache it
2063
            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2064
                throw new EE_Error(sprintf(
2065
                    __('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',
2066
                        'event_espresso'),
2067
                    $otherObjectModelObjectOrID,
2068
                    get_class($this)
2069
                ));
2070
            } else {
2071
                $otherObject = $otherObjectModelObjectOrID;
2072
            }
2073
            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2074
        }
2075
        if ($otherObject instanceof EE_Base_Class) {
2076
            //fix the reciprocal relation too
2077
            if ($otherObject->ID()) {
2078
                //its saved so assumed relations exist in the DB, so we can just
2079
                //clear the cache so future queries use the updated info in the DB
2080
                $otherObject->clear_cache($model->get_this_model_name(), null, true);
2081
            } else {
2082
                //it's not saved, so it caches relations like this
2083
                $otherObject->cache($model->get_this_model_name(), $this);
2084
            }
2085
        }
2086
        return $otherObject;
2087
    }
2088
2089
2090
2091
    /**
2092
     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2093
     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2094
     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2095
     * from the cache
2096
     *
2097
     * @param mixed  $otherObjectModelObjectOrID
2098
     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2099
     *                to the DB yet
2100
     * @param string $relationName
2101
     * @param array  $where_query
2102
     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2103
     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2104
     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2105
     *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2106
     *                created in the join table.
2107
     * @return EE_Base_Class the relation was removed from
2108
     * @throws \EE_Error
2109
     */
2110
    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2111
    {
2112
        if ($this->ID()) {
2113
            //if this exists in the DB, save the relation change to the DB too
2114
            $otherObject = $this->get_model()
2115
                                ->remove_relationship_to($this, $otherObjectModelObjectOrID, $relationName,
2116
                                    $where_query);
2117
            $this->clear_cache($relationName, $otherObject);
2118
        } else {
2119
            //this doesn't exist in the DB, just remove it from the cache
2120
            $otherObject = $this->clear_cache($relationName, $otherObjectModelObjectOrID);
2121
        }
2122
        if ($otherObject instanceof EE_Base_Class) {
2123
            $otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2124
        }
2125
        return $otherObject;
2126
    }
2127
2128
2129
2130
    /**
2131
     * Removes ALL the related things for the $relationName.
2132
     *
2133
     * @param string $relationName
2134
     * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
2135
     * @return EE_Base_Class
2136
     * @throws \EE_Error
2137
     */
2138
    public function _remove_relations($relationName, $where_query_params = array())
2139
    {
2140
        if ($this->ID()) {
2141
            //if this exists in the DB, save the relation change to the DB too
2142
            $otherObjects = $this->get_model()->remove_relations($this, $relationName, $where_query_params);
2143
            $this->clear_cache($relationName, null, true);
2144
        } else {
2145
            //this doesn't exist in the DB, just remove it from the cache
2146
            $otherObjects = $this->clear_cache($relationName, null, true);
2147
        }
2148
        if (is_array($otherObjects)) {
2149
            foreach ($otherObjects as $otherObject) {
2150
                $otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2151
            }
2152
        }
2153
        return $otherObjects;
2154
    }
2155
2156
2157
2158
    /**
2159
     * Gets all the related model objects of the specified type. Eg, if the current class if
2160
     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2161
     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2162
     * because we want to get even deleted items etc.
2163
     *
2164
     * @param string $relationName key in the model's _model_relations array
2165
     * @param array  $query_params like EEM_Base::get_all
2166
     * @return EE_Base_Class[] Results not necessarily indexed by IDs, because some results might not have primary keys
2167
     * @throws \EE_Error
2168
     *                             or might not be saved yet. Consider using EEM_Base::get_IDs() on these results if
2169
     *                             you want IDs
2170
     */
2171
    public function get_many_related($relationName, $query_params = array())
2172
    {
2173
        if ($this->ID()) {
2174
            //this exists in the DB, so get the related things from either the cache or the DB
2175
            //if there are query parameters, forget about caching the related model objects.
2176
            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...
2177
                $related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
2178
            } else {
2179
                //did we already cache the result of this query?
2180
                $cached_results = $this->get_all_from_cache($relationName);
2181
                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...
2182
                    $related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
2183
                    //if no query parameters were passed, then we got all the related model objects
2184
                    //for that relation. We can cache them then.
2185
                    foreach ($related_model_objects as $related_model_object) {
2186
                        $this->cache($relationName, $related_model_object);
2187
                    }
2188
                } else {
2189
                    $related_model_objects = $cached_results;
2190
                }
2191
            }
2192
        } else {
2193
            //this doesn't exist in the DB, so just get the related things from the cache
2194
            $related_model_objects = $this->get_all_from_cache($relationName);
2195
        }
2196
        return $related_model_objects;
2197
    }
2198
2199
2200
2201
    /**
2202
     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2203
     * unless otherwise specified in the $query_params
2204
     *
2205
     * @param string $relation_name  model_name like 'Event', or 'Registration'
2206
     * @param array  $query_params   like EEM_Base::get_all's
2207
     * @param string $field_to_count name of field to count by. By default, uses primary key
2208
     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2209
     *                               that by the setting $distinct to TRUE;
2210
     * @return int
2211
     */
2212
    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2213
    {
2214
        return $this->get_model()->count_related($this, $relation_name, $query_params, $field_to_count, $distinct);
2215
    }
2216
2217
2218
2219
    /**
2220
     * Instead of getting the related model objects, simply sums up the values of the specified field.
2221
     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2222
     *
2223
     * @param string $relation_name model_name like 'Event', or 'Registration'
2224
     * @param array  $query_params  like EEM_Base::get_all's
2225
     * @param string $field_to_sum  name of field to count by.
2226
     *                              By default, uses primary key (which doesn't make much sense, so you should probably
2227
     *                              change it)
2228
     * @return int
2229
     */
2230
    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2231
    {
2232
        return $this->get_model()->sum_related($this, $relation_name, $query_params, $field_to_sum);
2233
    }
2234
2235
2236
2237
    /**
2238
     * Gets the first (ie, one) related model object of the specified type.
2239
     *
2240
     * @param string $relationName key in the model's _model_relations array
2241
     * @param array  $query_params like EEM_Base::get_all
2242
     * @return EE_Base_Class (not an array, a single object)
2243
     * @throws \EE_Error
2244
     */
2245
    public function get_first_related($relationName, $query_params = array())
2246
    {
2247
        $model = $this->get_model();
2248
        if ($this->ID()) {//this exists in the DB, get from the cache OR the DB
2249
            //if they've provided some query parameters, don't bother trying to cache the result
2250
            //also make sure we're not caching the result of get_first_related
2251
            //on a relation which should have an array of objects (because the cache might have an array of objects)
2252
            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...
2253
                || ! $model->related_settings_for($relationName)
2254
                     instanceof
2255
                     EE_Belongs_To_Relation
2256
            ) {
2257
                $related_model_object = $model->get_first_related($this, $relationName, $query_params);
2258
            } else {
2259
                //first, check if we've already cached the result of this query
2260
                $cached_result = $this->get_one_from_cache($relationName);
2261
                if ( ! $cached_result) {
2262
                    $related_model_object = $model->get_first_related($this, $relationName, $query_params);
2263
                    $this->cache($relationName, $related_model_object);
2264
                } else {
2265
                    $related_model_object = $cached_result;
2266
                }
2267
            }
2268
        } else {
2269
            $related_model_object = null;
2270
            //this doesn't exist in the Db, but maybe the relation is of type belongs to, and so the related thing might
2271
            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2272
                $related_model_object = $model->get_first_related($this, $relationName, $query_params);
2273
            }
2274
            //this doesn't exist in the DB and apparently the thing it belongs to doesn't either, just get what's cached on this object
2275
            if ( ! $related_model_object) {
2276
                $related_model_object = $this->get_one_from_cache($relationName);
2277
            }
2278
        }
2279
        return $related_model_object;
2280
    }
2281
2282
2283
2284
    /**
2285
     * Does a delete on all related objects of type $relationName and removes
2286
     * the current model object's relation to them. If they can't be deleted (because
2287
     * of blocking related model objects) does nothing. If the related model objects are
2288
     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2289
     * If this model object doesn't exist yet in the DB, just removes its related things
2290
     *
2291
     * @param string $relationName
2292
     * @param array  $query_params like EEM_Base::get_all's
2293
     * @return int how many deleted
2294
     * @throws \EE_Error
2295
     */
2296 View Code Duplication
    public function delete_related($relationName, $query_params = array())
2297
    {
2298
        if ($this->ID()) {
2299
            $count = $this->get_model()->delete_related($this, $relationName, $query_params);
2300
        } else {
2301
            $count = count($this->get_all_from_cache($relationName));
2302
            $this->clear_cache($relationName, null, true);
2303
        }
2304
        return $count;
2305
    }
2306
2307
2308
2309
    /**
2310
     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2311
     * the current model object's relation to them. If they can't be deleted (because
2312
     * of blocking related model objects) just does a soft delete on it instead, if possible.
2313
     * If the related thing isn't a soft-deletable model object, this function is identical
2314
     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2315
     *
2316
     * @param string $relationName
2317
     * @param array  $query_params like EEM_Base::get_all's
2318
     * @return int how many deleted (including those soft deleted)
2319
     * @throws \EE_Error
2320
     */
2321 View Code Duplication
    public function delete_related_permanently($relationName, $query_params = array())
2322
    {
2323
        if ($this->ID()) {
2324
            $count = $this->get_model()->delete_related_permanently($this, $relationName, $query_params);
2325
        } else {
2326
            $count = count($this->get_all_from_cache($relationName));
2327
        }
2328
        $this->clear_cache($relationName, null, true);
2329
        return $count;
2330
    }
2331
2332
2333
2334
    /**
2335
     * is_set
2336
     * Just a simple utility function children can use for checking if property exists
2337
     *
2338
     * @access  public
2339
     * @param  string $field_name property to check
2340
     * @return bool                              TRUE if existing,FALSE if not.
2341
     */
2342
    public function is_set($field_name)
2343
    {
2344
        return isset($this->_fields[$field_name]);
2345
    }
2346
2347
2348
2349
    /**
2350
     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2351
     * EE_Error exception if they don't
2352
     *
2353
     * @param  mixed (string|array) $properties properties to check
2354
     * @throws EE_Error
2355
     * @return bool                              TRUE if existing, throw EE_Error if not.
2356
     */
2357
    protected function _property_exists($properties)
2358
    {
2359
        foreach ((array)$properties as $property_name) {
2360
            //first make sure this property exists
2361
            if ( ! $this->_fields[$property_name]) {
2362
                throw new EE_Error(
2363
                    sprintf(
2364
                        __(
2365
                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2366
                            'event_espresso'
2367
                        ),
2368
                        $property_name
2369
                    )
2370
                );
2371
            }
2372
        }
2373
        return true;
2374
    }
2375
2376
2377
2378
    /**
2379
     * This simply returns an array of model fields for this object
2380
     *
2381
     * @return array
2382
     * @throws \EE_Error
2383
     */
2384
    public function model_field_array()
2385
    {
2386
        $fields = $this->get_model()->field_settings(false);
2387
        $properties = array();
2388
        //remove prepended underscore
2389
        foreach ($fields as $field_name => $settings) {
2390
            $properties[$field_name] = $this->get($field_name);
2391
        }
2392
        return $properties;
2393
    }
2394
2395
2396
2397
    /**
2398
     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2399
     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2400
     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
2401
     * requiring a plugin to extend the EE_Base_Class (which works fine is there's only 1 plugin, but when will that
2402
     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
2403
     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
2404
     * was called, and an array of the original arguments passed to the function. Whatever their callback function
2405
     * returns will be returned by this function. Example: in functions.php (or in a plugin):
2406
     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
2407
     * my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2408
     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2409
     *        return $previousReturnValue.$returnString;
2410
     * }
2411
     * require('EE_Answer.class.php');
2412
     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2413
     * echo $answer->my_callback('monkeys',100);
2414
     * //will output "you called my_callback! and passed args:monkeys,100"
2415
     *
2416
     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2417
     * @param array  $args       array of original arguments passed to the function
2418
     * @throws EE_Error
2419
     * @return mixed whatever the plugin which calls add_filter decides
2420
     */
2421
    public function __call($methodName, $args)
2422
    {
2423
        $className = get_class($this);
2424
        $tagName = "FHEE__{$className}__{$methodName}";
2425
        if ( ! has_filter($tagName)) {
2426
            throw new EE_Error(
2427
                sprintf(
2428
                    __(
2429
                        "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;}",
2430
                        "event_espresso"
2431
                    ),
2432
                    $methodName,
2433
                    $className,
2434
                    $tagName
2435
                )
2436
            );
2437
        }
2438
        return apply_filters($tagName, null, $this, $args);
2439
    }
2440
2441
2442
2443
    /**
2444
     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2445
     * A $previous_value can be specified in case there are many meta rows with the same key
2446
     *
2447
     * @param string $meta_key
2448
     * @param mixed  $meta_value
2449
     * @param mixed  $previous_value
2450
     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2451
     * @throws \EE_Error
2452
     * NOTE: if the values haven't changed, returns 0
2453
     */
2454
    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2455
    {
2456
        $query_params = array(
2457
            array(
2458
                'EXM_key'  => $meta_key,
2459
                'OBJ_ID'   => $this->ID(),
2460
                'EXM_type' => $this->get_model()->get_this_model_name(),
2461
            ),
2462
        );
2463
        if ($previous_value !== null) {
2464
            $query_params[0]['EXM_value'] = $meta_value;
2465
        }
2466
        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2467
        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...
2468
            return $this->add_extra_meta($meta_key, $meta_value);
2469
        }
2470
        foreach ($existing_rows_like_that as $existing_row) {
2471
            $existing_row->save(array('EXM_value' => $meta_value));
2472
        }
2473
        return count($existing_rows_like_that);
2474
    }
2475
2476
2477
2478
    /**
2479
     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2480
     * no other extra meta for this model object have the same key. Returns TRUE if the
2481
     * extra meta row was entered, false if not
2482
     *
2483
     * @param string  $meta_key
2484
     * @param mixed   $meta_value
2485
     * @param boolean $unique
2486
     * @return boolean
2487
     * @throws \EE_Error
2488
     */
2489
    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2490
    {
2491
        if ($unique) {
2492
            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2493
                array(
2494
                    array(
2495
                        'EXM_key'  => $meta_key,
2496
                        'OBJ_ID'   => $this->ID(),
2497
                        'EXM_type' => $this->get_model()->get_this_model_name(),
2498
                    ),
2499
                )
2500
            );
2501
            if ($existing_extra_meta) {
2502
                return false;
2503
            }
2504
        }
2505
        $new_extra_meta = EE_Extra_Meta::new_instance(
2506
            array(
2507
                'EXM_key'   => $meta_key,
2508
                'EXM_value' => $meta_value,
2509
                'OBJ_ID'    => $this->ID(),
2510
                'EXM_type'  => $this->get_model()->get_this_model_name(),
2511
            )
2512
        );
2513
        $new_extra_meta->save();
2514
        return true;
2515
    }
2516
2517
2518
2519
    /**
2520
     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2521
     * is specified, only deletes extra meta records with that value.
2522
     *
2523
     * @param string $meta_key
2524
     * @param mixed  $meta_value
2525
     * @return int number of extra meta rows deleted
2526
     * @throws \EE_Error
2527
     */
2528
    public function delete_extra_meta($meta_key, $meta_value = null)
2529
    {
2530
        $query_params = array(
2531
            array(
2532
                'EXM_key'  => $meta_key,
2533
                'OBJ_ID'   => $this->ID(),
2534
                'EXM_type' => $this->get_model()->get_this_model_name(),
2535
            ),
2536
        );
2537
        if ($meta_value !== null) {
2538
            $query_params[0]['EXM_value'] = $meta_value;
2539
        }
2540
        return EEM_Extra_Meta::instance()->delete($query_params);
2541
    }
2542
2543
2544
2545
    /**
2546
     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2547
     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2548
     * You can specify $default is case you haven't found the extra meta
2549
     *
2550
     * @param string  $meta_key
2551
     * @param boolean $single
2552
     * @param mixed   $default if we don't find anything, what should we return?
2553
     * @return mixed single value if $single; array if ! $single
2554
     * @throws \EE_Error
2555
     */
2556
    public function get_extra_meta($meta_key, $single = false, $default = null)
2557
    {
2558
        if ($single) {
2559
            $result = $this->get_first_related('Extra_Meta', array(array('EXM_key' => $meta_key)));
2560
            if ($result instanceof EE_Extra_Meta) {
2561
                return $result->value();
2562
            }
2563
        } else {
2564
            $results = $this->get_many_related('Extra_Meta', array(array('EXM_key' => $meta_key)));
2565
            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...
2566
                $values = array();
2567
                foreach ($results as $result) {
2568
                    if ($result instanceof EE_Extra_Meta) {
2569
                        $values[$result->ID()] = $result->value();
2570
                    }
2571
                }
2572
                return $values;
2573
            }
2574
        }
2575
        //if nothing discovered yet return default.
2576
        return apply_filters(
2577
            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2578
            $default,
2579
            $meta_key,
2580
            $single,
2581
            $this
2582
            );
2583
    }
2584
2585
2586
2587
    /**
2588
     * Returns a simple array of all the extra meta associated with this model object.
2589
     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2590
     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2591
     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2592
     * If $one_of_each_key is false, it will return an array with the top-level keys being
2593
     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2594
     * finally the extra meta's value as each sub-value. (eg
2595
     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2596
     *
2597
     * @param boolean $one_of_each_key
2598
     * @return array
2599
     * @throws \EE_Error
2600
     */
2601
    public function all_extra_meta_array($one_of_each_key = true)
2602
    {
2603
        $return_array = array();
2604
        if ($one_of_each_key) {
2605
            $extra_meta_objs = $this->get_many_related('Extra_Meta', array('group_by' => 'EXM_key'));
2606
            foreach ($extra_meta_objs as $extra_meta_obj) {
2607
                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2608
                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2609
                }
2610
            }
2611
        } else {
2612
            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2613
            foreach ($extra_meta_objs as $extra_meta_obj) {
2614
                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2615
                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2616
                        $return_array[$extra_meta_obj->key()] = array();
2617
                    }
2618
                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2619
                }
2620
            }
2621
        }
2622
        return $return_array;
2623
    }
2624
2625
2626
2627
    /**
2628
     * Gets a pretty nice displayable nice for this model object. Often overridden
2629
     *
2630
     * @return string
2631
     * @throws \EE_Error
2632
     */
2633
    public function name()
2634
    {
2635
        //find a field that's not a text field
2636
        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2637
        if ($field_we_can_use) {
2638
            return $this->get($field_we_can_use->get_name());
2639
        } else {
2640
            $first_few_properties = $this->model_field_array();
2641
            $first_few_properties = array_slice($first_few_properties, 0, 3);
2642
            $name_parts = array();
2643
            foreach ($first_few_properties as $name => $value) {
2644
                $name_parts[] = "$name:$value";
2645
            }
2646
            return implode(",", $name_parts);
2647
        }
2648
    }
2649
2650
2651
2652
    /**
2653
     * in_entity_map
2654
     * Checks if this model object has been proven to already be in the entity map
2655
     *
2656
     * @return boolean
2657
     * @throws \EE_Error
2658
     */
2659
    public function in_entity_map()
2660
    {
2661
        if ($this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this) {
2662
            //well, if we looked, did we find it in the entity map?
2663
            return true;
2664
        } else {
2665
            return false;
2666
        }
2667
    }
2668
2669
2670
2671
    /**
2672
     * refresh_from_db
2673
     * Makes sure the fields and values on this model object are in-sync with what's in the database.
2674
     *
2675
     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2676
     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2677
     */
2678
    public function refresh_from_db()
2679
    {
2680
        if ($this->ID() && $this->in_entity_map()) {
2681
            $this->get_model()->refresh_entity_map_from_db($this->ID());
2682
        } else {
2683
            //if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2684
            //if it has an ID but it's not in the map, and you're asking me to refresh it
2685
            //that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2686
            //absolutely nothing in it for this ID
2687
            if (WP_DEBUG) {
2688
                throw new EE_Error(
2689
                    sprintf(
2690
                        __('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.',
2691
                            'event_espresso'),
2692
                        $this->ID(),
2693
                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2694
                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2695
                    )
2696
                );
2697
            }
2698
        }
2699
    }
2700
2701
2702
2703
    /**
2704
     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
2705
     * (probably a bad assumption they have made, oh well)
2706
     *
2707
     * @return string
2708
     */
2709
    public function __toString()
2710
    {
2711
        try {
2712
            return sprintf('%s (%s)', $this->name(), $this->ID());
2713
        } catch (Exception $e) {
2714
            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
2715
            return '';
2716
        }
2717
    }
2718
2719
2720
2721
    /**
2722
     * Clear related model objects if they're already in the DB, because otherwise when we
2723
     * UN-serialize this model object we'll need to be careful to add them to the entity map.
2724
     * This means if we have made changes to those related model objects, and want to unserialize
2725
     * the this model object on a subsequent request, changes to those related model objects will be lost.
2726
     * Instead, those related model objects should be directly serialized and stored.
2727
     * Eg, the following won't work:
2728
     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2729
     * $att = $reg->attendee();
2730
     * $att->set( 'ATT_fname', 'Dirk' );
2731
     * update_option( 'my_option', serialize( $reg ) );
2732
     * //END REQUEST
2733
     * //START NEXT REQUEST
2734
     * $reg = get_option( 'my_option' );
2735
     * $reg->attendee()->save();
2736
     * And would need to be replace with:
2737
     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2738
     * $att = $reg->attendee();
2739
     * $att->set( 'ATT_fname', 'Dirk' );
2740
     * update_option( 'my_option', serialize( $reg ) );
2741
     * //END REQUEST
2742
     * //START NEXT REQUEST
2743
     * $att = get_option( 'my_option' );
2744
     * $att->save();
2745
     *
2746
     * @return array
2747
     * @throws \EE_Error
2748
     */
2749
    public function __sleep()
2750
    {
2751
        $model = $this->get_model();
2752
        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
2753
            if ($relation_obj instanceof EE_Belongs_To_Relation) {
2754
                $classname = 'EE_' . $model->get_this_model_name();
2755
                if (
2756
                    $this->get_one_from_cache($relation_name) instanceof $classname
2757
                    && $this->get_one_from_cache($relation_name)->ID()
2758
                ) {
2759
                    $this->clear_cache($relation_name, $this->get_one_from_cache($relation_name)->ID());
2760
                }
2761
            }
2762
        }
2763
        $this->_props_n_values_provided_in_constructor = array();
2764
        $properties_to_serialize = get_object_vars($this);
2765
        //don't serialize the model. It's big and that risks recursion
2766
        unset($properties_to_serialize['_model']);
2767
        return array_keys($properties_to_serialize);
2768
    }
2769
2770
2771
2772
    /**
2773
     * restore _props_n_values_provided_in_constructor
2774
     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
2775
     * and therefore should NOT be used to determine if state change has occurred since initial construction.
2776
     * At best, you would only be able to detect if state change has occurred during THIS request.
2777
     */
2778
    public function __wakeup()
2779
    {
2780
        $this->_props_n_values_provided_in_constructor = $this->_fields;
2781
    }
2782
2783
2784
2785
}
2786
2787
2788