Completed
Branch FET/4986/11317/order-by-count (7e1003)
by
unknown
45:30 queued 27:38
created

EE_Base_Class::getCustomSelect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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