Completed
Branch FET-8385-datetime-ticket-selec... (304b56)
by
unknown
51:42 queued 39:36
created

EE_Base_Class::__wakeup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php if (!defined('EVENT_ESPRESSO_VERSION')) {exit('No direct script access allowed');}
2
do_action( 'AHEE_log', __FILE__, ' FILE LOADED', '' );
3
/**
4
 *
5
 * Event Espresso
6
 *
7
 * Event Registration and Management Plugin for WordPress
8
 *
9
 * @ package			Event Espresso
10
 * @ author				Seth Shoultes
11
 * @ copyright		(c) 2008-2011 Event Espresso  All Rights Reserved.
12
 * @ license			http://eventespresso.com/support/terms-conditions/   * see Plugin Licensing *
13
 * @ link					http://www.eventespresso.com
14
 * @ version		 	4.0
15
 *
16
 * ------------------------------------------------------------------------
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
 */
26
27
abstract class EE_Base_Class {
28
29
	/**
30
	 * This is an array of the original properties and values provided during construction
31
	 * of this model object. (keys are model field names, values are their values).
32
	 * This list is important to remember so that when we are merging data from the db, we know
33
	 * which values to override and which to not override.
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.  This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have access to it.
41
	 * @var string
42
	 */
43
	protected $_timezone;
44
45
46
47
	/**
48
	 * date format
49
	 * pattern or format for displaying dates
50
	 *
51
	 * @var string $_dt_frmt
52
	 */
53
	protected $_dt_frmt;
54
55
56
57
	/**
58
	 * time format
59
	 * pattern or format for displaying time
60
	 *
61
	 * @var string $_tm_frmt
62
	 */
63
	protected $_tm_frmt;
64
65
66
67
68
	/**
69
	 * This property is for holding a cached array of object properties indexed by property name as the key.
70
	 * The purpose of this is for setting a cache on properties that may have calculated values after a prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having to recalculate.
71
	 *
72
	 * Used by _set_cached_property() and _get_cached_property() methods.
73
	 *
74
	 * @var array
75
	 */
76
	protected $_cached_properties = array();
77
78
	/**
79
     * An array containing keys of the related model, and values are either an array of related mode objects or a single
80
     * related model object. see the model's _model_relations. The keys should match those specified. And if the relation
81
     * is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object, all others have an array)
82
     * @var array
83
     */
84
	protected $_model_relations = array();
85
86
	/**
87
	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
88
	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
89
	 * @var array
90
	 */
91
	protected $_fields = array();
92
93
	/**
94
	 *
95
	 * @var boolean indicating whether or not this model object is intended to ever be saved
96
	 * For example, we might create model objects intended to only be used for the duration
97
	 * of this request and to be thrown away, and if they were accidentally saved
98
	 * it would be a bug.
99
	 */
100
	protected $_allow_persist = true;
101
102
103
104
	/**
105
	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children play nice
106
	 *
107
	 * @param array 		$fieldValues where each key is a field (ie, array key in the 2nd layer of the model's _fields array, (eg, EVT_ID, TXN_amount, QST_name, etc) and values are their values
108
	 * @param boolean 	$bydb 			a flag for setting if the class is instantiated by the corresponding db model or not.
109
	 * @param string 		$timezone 	indicate what timezone you want any datetime fields to be in when instantiating a EE_Base_Class object.
110
	 * @param array                      $date_formats An array of date formats to set on construct where first
111
	 *                                                 		 value is the date_format and second value is the time
112
	 *                                                 		 format.
113
	 * @throws EE_Error
114
	 */
115
	protected function __construct( $fieldValues = array(), $bydb = FALSE, $timezone = '', $date_formats = array() ){
116
117
		$className=get_class($this);
118
119
		do_action("AHEE__{$className}__construct",$this,$fieldValues);
120
		$model=$this->get_model();
121
		$model_fields = $model->field_settings( FALSE );
122
		// ensure $fieldValues is an array
123
		$fieldValues = is_array( $fieldValues ) ? $fieldValues : array( $fieldValues );
124
		// EEH_Debug_Tools::printr( $fieldValues, '$fieldValues  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
125
		// verify client code has not passed any invalid field names
126
		foreach($fieldValues as $field_name=> $field_value){
127 View Code Duplication
			if( ! isset( $model_fields[ $field_name] ) ){
128
				throw new EE_Error(sprintf(__("Invalid field (%s) passed to constructor of %s. Allowed fields are :%s", "event_espresso"),$field_name,get_class($this),implode(", ",array_keys($model_fields))));
129
			}
130
		}
131
		// EEH_Debug_Tools::printr( $model_fields, '$model_fields  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
132
		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string( $timezone );
133
134
		if ( ! empty( $date_formats ) && is_array( $date_formats ) ) {
135
			list( $this->_dt_frmt, $this->_tm_frmt ) = $date_formats;
136
		} else {
137
			//set default formats for date and time
138
			$this->_dt_frmt = (string) get_option( 'date_format', 'Y-m-d' );
139
			$this->_tm_frmt = (string) get_option( 'time_format', 'g:i a' );
140
		}
141
142
		//if db model is instantiating
143
		if ( $bydb ){
144
			//client code has indicated these field values are from the database
145 View Code Duplication
			foreach( $model_fields as $fieldName => $field ){
146
				$this->set_from_db( $fieldName, isset( $fieldValues[ $fieldName] ) ? $fieldValues[ $fieldName ] : null );
147
			}
148
		} else {
149
			//we're constructing a brand
150
			//new instance of the model object. Generally, this means we'll need to do more field validation
151 View Code Duplication
			foreach( $model_fields as $fieldName => $field ){
152
				$this->set( $fieldName, isset( $fieldValues[ $fieldName ] ) ? $fieldValues[ $fieldName ] : null, true );
153
			}
154
		}
155
156
		//remember what values were passed to this constructor
157
		$this->_props_n_values_provided_in_constructor = $fieldValues;
158
		//remember in entity mapper
159
		if( ! $bydb  && $model->has_primary_key_field() && $this->ID() ){
160
			$model->add_to_entity_map($this);
161
		}
162
		//setup all the relations
163
		foreach($this->get_model()->relation_settings() as $relation_name=>$relation_obj){
164
			if($relation_obj instanceof EE_Belongs_To_Relation){
165
				$this->_model_relations[$relation_name] = NULL;
166
			}else{
167
				$this->_model_relations[$relation_name] = array();
168
			}
169
		}
170
		/**
171
		 * Action done at the end of each model object construction
172
		 * @param EE_Base_Class $this the model object just created
173
		 */
174
		do_action( 'AHEE__EE_Base_Class__construct__finished', $this );
175
	}
176
177
	/**
178
	 * Gets whether or not this model object is allowed to persist/be saved to the database.
179
	 * @return boolean
180
	 */
181
	public function allow_persist() {
182
		return $this->_allow_persist;
183
	}
184
185
186
187
	/**
188
	 * Sets whether or not this model object should be allowed to be saved to the DB.
189
	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
190
	 * you got new information that somehow made you change your mind.
191
	 * @param boolean $allow_persist
192
	 * @return boolean
193
	 */
194
	public function set_allow_persist( $allow_persist ) {
195
		return $this->_allow_persist = $allow_persist;
196
	}
197
198
199
200
	/**
201
	 * Gets the field's original value when this object was constructed during this request.
202
	 * This can be helpful when determining if a model object has changed or not
203
	 *
204
	 * @param string $field_name
205
	 * @return mixed|null
206
	 * @throws \EE_Error
207
	 */
208
	public function get_original( $field_name ){
209
		if( isset( $this->_props_n_values_provided_in_constructor[ $field_name ] ) &&
210
				$field_settings = $this->get_model()->field_settings_for( $field_name )){
211
			return $field_settings->prepare_for_get( $this->_props_n_values_provided_in_constructor[ $field_name ] );
212
		}else{
213
			return NULL;
214
		}
215
	}
216
217
218
	/**
219
	 * @param EE_Base_Class $obj
220
	 * @return string
221
	 */
222
	public function get_class($obj){
223
		return get_class($obj);
224
	}
225
226
227
228
	/**
229
	 * Overrides parent because parent expects old models.
230
	 * This also doesn't do any validation, and won't work for serialized arrays
231
	 *
232
	 * @param    string $field_name
233
	 * @param    mixed  $field_value
234
	 * @param bool      $use_default
235
	 * @throws \EE_Error
236
	 */
237
	public function set( $field_name, $field_value, $use_default = FALSE ){
238
		$field_obj = $this->get_model()->field_settings_for( $field_name );
239
		if ( $field_obj instanceof EE_Model_Field_Base ) {
240
//			if ( method_exists( $field_obj, 'set_timezone' )) {
241 View Code Duplication
			if ( $field_obj instanceof EE_Datetime_Field ) {
242
				$field_obj->set_timezone( $this->_timezone );
243
				$field_obj->set_date_format( $this->_dt_frmt );
244
				$field_obj->set_time_format( $this->_tm_frmt );
245
			}
246
247
			$holder_of_value = $field_obj->prepare_for_set($field_value);
248
			//should the value be null?
249
			if( ($field_value === NULL || $holder_of_value === NULL || $holder_of_value ==='') && $use_default){
250
				$this->_fields[$field_name] = $field_obj->get_default_value();
251
252
				/**
253
				 * To save having to refactor all the models, if a default value is used for a
254
				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
255
				 * object.  Then let's do a set again to ensure that it becomes a DateTime
256
				 * object.
257
				 * @since 4.6.10+
258
				 */
259
				if (
260
					$field_obj instanceof EE_Datetime_Field
261
					&& $this->_fields[ $field_name ] !== null
262
					&& ! $this->_fields[$field_name] instanceof DateTime
263
				) {
264
					empty( $this->_fields[$field_name] )
265
						? $this->set( $field_name, time() )
266
						: $this->set( $field_name, $this->_fields[$field_name] );
267
				}
268
269
			}else{
270
				$this->_fields[$field_name] = $holder_of_value;
271
			}
272
273
			//if we're not in the constructor...
274
			//now check if what we set was a primary key
275
			if(
276
				//note: props_n_values_provided_in_constructor is only set at the END of the constructor
277
				$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...
278
				&& $field_value
279
				&& $field_name === self::_get_primary_key_name( get_class( $this ) )
280
			){
281
				//if so, we want all this object's fields to be filled either with
282
				//what we've explicitly set on this model
283
				//or what we have in the db
284
				// echo "setting primary key!";
285
				$fields_on_model = self::_get_model(get_class($this))->field_settings();
286
287
				$obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
288
				foreach($fields_on_model as $field_obj){
289
					if( ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
290
						&& $field_obj->get_name() !== $field_name ){
291
292
						$this->set($field_obj->get_name(),$obj_in_db->get($field_obj->get_name()));
293
					}
294
				}
295
				//oh this model object has an ID? well make sure its in the entity mapper
296
				$this->get_model()->add_to_entity_map($this);
297
			}
298
			//let's unset any cache for this field_name from the $_cached_properties property.
299
			$this->_clear_cached_property( $field_name );
300
		}else{
301
			throw new EE_Error( sprintf( __( "A valid EE_Model_Field_Base could not be found for the given field name: %s", "event_espresso" ), $field_name  ) );
302
		}
303
304
	}
305
306
307
308
	/**
309
	 * This sets the field value on the db column if it exists for the given $column_name or
310
	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
311
	 *
312
	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
313
	 * @param string $field_name  Must be the exact column name.
314
	 * @param mixed  $field_value The value to set.
315
	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
316
	 * @throws \EE_Error
317
	 */
318
	public function set_field_or_extra_meta( $field_name, $field_value ) {
319
		if ( $this->get_model()->has_field( $field_name ) ) {
320
			$this->set( $field_name, $field_value );
321
			return true;
322
		} else {
323
			//ensure this object is saved first so that extra meta can be properly related.
324
			$this->save();
325
			return $this->update_extra_meta( $field_name, $field_value );
326
		}
327
	}
328
329
330
331
	/**
332
	 * This retrieves the value of the db column set on this class or if that's not present
333
	 * it will attempt to retrieve from extra_meta if found.
334
	 * Example Usage:
335
	 * Via EE_Message child class:
336
	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
337
	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
338
	 * also have additional main fields specific to the messenger.  The system accommodates those extra
339
	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
340
	 * value for those extra fields dynamically via the EE_message object.
341
	 *
342
	 * @param  string $field_name expecting the fully qualified field name.
343
	 * @return mixed|null  value for the field if found.  null if not found.
344
	 * @throws \EE_Error
345
	 */
346
	public function get_field_or_extra_meta( $field_name ) {
347
		if ( $this->get_model()->has_field( $field_name ) ) {
348
			$column_value = $this->get( $field_name );
349
		} else {
350
			//This isn't a column in the main table, let's see if it is in the extra meta.
351
			$column_value = $this->get_extra_meta( $field_name, true, null );
352
		}
353
		return $column_value;
354
	}
355
356
357
358
	/**
359
	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally for being able to reference what timezone we are running conversions on when converting TO the internal timezone (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp).
360
	 *  This is available to all child classes that may be using the EE_Datetime_Field for a field data type.
361
	 *
362
	 * @access public
363
	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
364
	 * @return void
365
	 * @throws \EE_Error
366
	 */
367
	public function set_timezone( $timezone = '' ) {
368
		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string( $timezone );
369
		//make sure we clear all cached properties because they won't be relevant now
370
		$this->_clear_cached_properties();
371
372
		//make sure we update field settings and the date for all EE_Datetime_Fields
373
		$model_fields = $this->get_model()->field_settings( false );
374
		foreach ( $model_fields as $field_name => $field_obj ) {
375
			if ( $field_obj instanceof EE_Datetime_Field ) {
376
				$field_obj->set_timezone( $this->_timezone );
377
				if ( isset( $this->_fields[$field_name] ) && $this->_fields[$field_name] instanceof DateTime ) {
378
					$this->_fields[$field_name]->setTimezone( new DateTimeZone( $this->_timezone ) );
379
				}
380
			}
381
		}
382
	}
383
384
385
386
387
	/**
388
	 * This just returns whatever is set for the current timezone.
389
	 *
390
	 * @access public
391
	 * @return string timezone string
392
	 */
393
	public function get_timezone() {
394
		return $this->_timezone;
395
	}
396
397
398
399
	/**
400
	 * This sets the internal date format to what is sent in to be used as the new default for the class
401
	 * internally instead of wp set date format options
402
	 *
403
	 * @since 4.6
404
	 *
405
	 * @param string $format   should be a format recognizable by PHP date() functions.
406
	 */
407
	public function set_date_format( $format ) {
408
		$this->_dt_frmt = $format;
409
		//clear cached_properties because they won't be relevant now.
410
		$this->_clear_cached_properties();
411
	}
412
413
414
415
416
	/**
417
	 * This sets the internal time format string to what is sent in to be used as the new default for the
418
	 * class internally instead of wp set time format options.
419
	 *
420
	 * @since 4.6
421
	 * @param string $format should be a format recognizable by PHP date() functions.
422
	 */
423
	public function set_time_format( $format ) {
424
		$this->_tm_frmt = $format;
425
		//clear cached_properties because they won't be relevant now.
426
		$this->_clear_cached_properties();
427
	}
428
429
430
431
432
	/**
433
	 * This returns the current internal set format for the date and time formats.
434
	 *
435
	 * @param bool $full   if true (default), then return the full format.  Otherwise will return an array where the
436
	 *                     		 first value is the date format and the second value is the time format.
437
	 *
438
	 * @return mixed string|array
439
	 */
440
	public function get_format( $full = true ) {
441
		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array( $this->_dt_frmt, $this->_tm_frmt );
442
	}
443
444
445
446
447
448
	/**
449
	 * cache
450
	 * stores the passed model object on the current model object.
451
	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
452
	 *
453
	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg 'Registration' associated with this model object
454
	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction, that could be a payment or a registration)
455
	 * @param null          $cache_id a string or number that will be used as the key for any Belongs_To_Many items which will be stored in an array on this object
456
	 * @throws EE_Error
457
	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one related thing, no array)
458
	 */
459
	public function cache( $relationName = '', $object_to_cache = NULL, $cache_id = NULL ){
460
		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
461
		if ( ! $object_to_cache instanceof EE_Base_Class ) {
462
			return FALSE;
463
		}
464
		// also get "how" the object is related, or throw an error
465
		if( ! $relationship_to_model = $this->get_model()->related_settings_for( $relationName )) {
466
			throw new EE_Error( sprintf( __( 'There is no relationship to %s on a %s. Cannot cache it', 'event_espresso' ), $relationName, get_class( $this )));
467
		}
468
		// how many things are related ?
469
		if( $relationship_to_model instanceof EE_Belongs_To_Relation ){
470
			// 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
471
			// so for these model objects just set it to be cached
472
			$this->_model_relations[$relationName] = $object_to_cache;
473
			$return = TRUE;
474
		} else {
475
			// 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.
476
			// eg: if this is an event, there are many registrations for that event, so we cache the registrations in an array
477
			if( ! is_array( $this->_model_relations[$relationName] )) {
478
				// if for some reason, the cached item is a model object, then stick that in the array, otherwise start with an empty array
479
				$this->_model_relations[$relationName] = $this->_model_relations[$relationName] instanceof EE_Base_Class ? array( $this->_model_relations[$relationName] ) : array();
480
			}
481
			// first check for a cache_id which is normally empty
482
			if ( ! empty( $cache_id )) {
483
				// 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
484
				$this->_model_relations[$relationName][ $cache_id ] = $object_to_cache;
485
				$return = $cache_id;
486
			} elseif ( $object_to_cache->ID() ) {
487
				// OR the cached object originally came from the db, so let's just use it's PK for an ID
488
				$this->_model_relations[$relationName][ $object_to_cache->ID() ] = $object_to_cache;
489
				$return = $object_to_cache->ID();
490
			} else {
491
				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
492
				$this->_model_relations[$relationName][] = $object_to_cache;
493
				  // move the internal pointer to the end of the array
494
				end( $this->_model_relations[$relationName] );
495
				// and grab the key so that we can return it
496
				$return = key( $this->_model_relations[$relationName] );
497
			}
498
499
		}
500
		return $return;
501
	}
502
503
504
505
	/**
506
	 * For adding an item to the cached_properties property.
507
	 *
508
	 * @access protected
509
	 * @param string      $fieldname the property item the corresponding value is for.
510
	 * @param mixed       $value     The value we are caching.
511
	 * @param string|null $cache_type
512
	 * @return void
513
	 * @throws \EE_Error
514
	 */
515
	protected function _set_cached_property( $fieldname, $value, $cache_type = NULL ) {
516
		//first make sure this property exists
517
		$this->get_model()->field_settings_for($fieldname);
518
519
		$cache_type = empty( $cache_type ) ? 'standard' : $cache_type;
520
		$this->_cached_properties[$fieldname][$cache_type] = $value;
521
	}
522
523
524
525
	/**
526
	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
527
	 * This also SETS the cache if we return the actual property!
528
	 *
529
	 * @param string  $fieldname       the name of the property we're trying to retrieve
530
	 * @param bool    $pretty
531
	 * @param string  $extra_cache_ref This allows the user to specify an extra cache ref for the given property
532
	 *                                 (in cases where the same property may be used for different outputs
533
	 *                                 - i.e. datetime, money etc.)
534
	 *                                 It can also accept certain pre-defined "schema" strings
535
	 *                                 to define how to output the property.
536
	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
537
	 * @return mixed                   whatever the value for the property is we're retrieving
538
	 * @throws \EE_Error
539
	 */
540
	protected function _get_cached_property( $fieldname, $pretty = FALSE, $extra_cache_ref = NULL ) {
541
		//verify the field exists
542
		$this->get_model()->field_settings_for($fieldname);
543
544
		$cache_type = $pretty ? 'pretty' : 'standard';
545
		$cache_type .= !empty( $extra_cache_ref ) ? '_' . $extra_cache_ref : '';
546
547
		if ( isset( $this->_cached_properties[$fieldname][$cache_type] ) ) {
548
			return $this->_cached_properties[$fieldname][$cache_type];
549
		}
550
551
		$field_obj = $this->get_model()->field_settings_for($fieldname);
552
		if ( $field_obj instanceof EE_Model_Field_Base ) {
553
			/**
554
			 * maybe this is EE_Datetime_Field.  If so we need to make sure timezone and
555
			 * formats are correct.
556
			 */
557 View Code Duplication
			if ( $field_obj instanceof EE_Datetime_Field ) {
558
				$field_obj->set_timezone( $this->_timezone );
559
				$field_obj->set_date_format( $this->_dt_frmt, $pretty );
560
				$field_obj->set_time_format( $this->_tm_frmt, $pretty );
561
			}
562
563
			if( ! isset($this->_fields[$fieldname])){
564
				$this->_fields[$fieldname] = NULL;
565
			}
566
			$value = $pretty
567
				? $field_obj->prepare_for_pretty_echoing($this->_fields[$fieldname], $extra_cache_ref)
0 ignored issues
show
Unused Code introduced by
The call to EE_Model_Field_Base::prepare_for_pretty_echoing() has too many arguments starting with $extra_cache_ref.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
568
				: $field_obj->prepare_for_get($this->_fields[$fieldname] );
569
			$this->_set_cached_property( $fieldname, $value, $cache_type );
570
			return $value;
571
		}
572
		return null;
573
	}
574
575
576
577
578
	/**
579
	 * This just takes care of clearing out the cached_properties
580
	 * @return void
581
	 */
582
	protected function _clear_cached_properties() {
583
		$this->_cached_properties = array();
584
	}
585
586
587
588
589
590
	/**
591
	 * This just clears out ONE property if it exists in the cache
592
	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
593
	 * @return void
594
	 */
595
	protected function _clear_cached_property( $property_name ) {
596
		if ( isset( $this->_cached_properties[ $property_name ] ) ) {
597
			unset( $this->_cached_properties[ $property_name ] );
598
		}
599
	}
600
601
602
603
	/**
604
	 * Ensures that this related thing is a model object.
605
	 *
606
	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
607
	 * @param string $model_name   name of the related thing, eg 'Attendee',
608
	 * @return EE_Base_Class
609
	 * @throws \EE_Error
610
	 */
611
	protected function ensure_related_thing_is_model_obj($object_or_id,$model_name){
612
		$other_model_instance = self::_get_model_instance_with_name(
613
			self::_get_model_classname( $model_name ),
614
			$this->_timezone
615
		);
616
		return $other_model_instance->ensure_is_obj( $object_or_id );
617
	}
618
619
620
621
	/**
622
	 * Forgets the cached model of the given relation Name. So the next time we request it,
623
	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
624
	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
625
	 * then only remove that one object from our cached array. Otherwise, clear the entire list
626
	 * @param string $relationName                         one of the keys in the _model_relations array on the model. Eg 'Registration'
627
	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
628
	 * if you intend to use $clear_all = TRUE, or the relation only has 1 object anyways (ie, it's a BelongsToRelation)
629
	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if this is HasMany or HABTM.
630
	 * @throws EE_Error
631
	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a relation from all
632
	 */
633
	public function clear_cache($relationName, $object_to_remove_or_index_into_array = NULL, $clear_all = FALSE){
634
		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
635
		$index_in_cache = '';
636
		if( ! $relationship_to_model){
637
			throw new EE_Error(
638
				sprintf(
639
					__( "There is no relationship to %s on a %s. Cannot clear that cache", 'event_espresso' ),
640
					$relationName,
641
					get_class( $this )
642
				)
643
			);
644
		}
645
		if($clear_all){
646
			$obj_removed = true;
647
			$this->_model_relations[$relationName]  = null;
648
		}elseif($relationship_to_model instanceof EE_Belongs_To_Relation){
649
			$obj_removed = $this->_model_relations[$relationName];
650
			$this->_model_relations[$relationName]  = null;
651
		}else{
652
			if($object_to_remove_or_index_into_array instanceof EE_Base_Class && $object_to_remove_or_index_into_array->ID()){
653
				$index_in_cache = $object_to_remove_or_index_into_array->ID();
654
				if( is_array($this->_model_relations[$relationName]) && ! isset($this->_model_relations[$relationName][$index_in_cache])){
655
					$index_found_at = NULL;
656
					//find this object in the array even though it has a different key
657
					foreach($this->_model_relations[$relationName] as $index=>$obj){
658
						if(
659
							$obj instanceof EE_Base_Class
660
							&& (
661
								$obj == $object_to_remove_or_index_into_array
662
								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
663
							)
664
						) {
665
							$index_found_at = $index;
666
							break;
667
						}
668
					}
669
					if($index_found_at){
670
						$index_in_cache = $index_found_at;
671
					}else{
672
						//it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
673
						//if it wasn't in it to begin with. So we're done
674
						return $object_to_remove_or_index_into_array;
675
					}
676
				}
677
			}elseif($object_to_remove_or_index_into_array instanceof EE_Base_Class){
678
				//so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
679
				foreach($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want){
680
					if($potentially_obj_we_want == $object_to_remove_or_index_into_array){
681
						$index_in_cache = $index;
682
					}
683
				}
684
			}else{
685
				$index_in_cache = $object_to_remove_or_index_into_array;
686
			}
687
			//supposedly we've found it. But it could just be that the client code
688
			//provided a bad index/object
689
			if (
690
				isset(
691
					$this->_model_relations[ $relationName ],
692
					$this->_model_relations[ $relationName ][ $index_in_cache ]
693
				)
694
			) {
695
				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
696
				unset( $this->_model_relations[ $relationName ][ $index_in_cache ] );
697
			} else {
698
				//that thing was never cached anyways.
699
				$obj_removed = null;
700
			}
701
		}
702
		return $obj_removed;
703
	}
704
705
706
707
	/**
708
	 * update_cache_after_object_save
709
	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has obtained after being saved to the db
710
	 *
711
	 * @param string         $relationName       - the type of object that is cached
712
	 * @param \EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
713
	 * @param string         $current_cache_id   - the ID that was used when originally caching the object
714
	 * @return boolean TRUE on success, FALSE on fail
715
	 * @throws \EE_Error
716
	 */
717
	public function update_cache_after_object_save( $relationName, EE_Base_Class $newly_saved_object, $current_cache_id = '') {
718
		// verify that incoming object is of the correct type
719
		$obj_class = 'EE_' . $relationName;
720
		if ( $newly_saved_object instanceof $obj_class ) {
721
			/* @type EE_Base_Class $newly_saved_object*/
722
			// now get the type of relation
723
			$relationship_to_model = $this->get_model()->related_settings_for( $relationName );
724
			// if this is a 1:1 relationship
725
			if( $relationship_to_model instanceof EE_Belongs_To_Relation ) {
726
				// then just replace the cached object with the newly saved object
727
				$this->_model_relations[$relationName] = $newly_saved_object;
728
				return TRUE;
729
			// or if it's some kind of sordid feral polyamorous relationship...
730
			} elseif ( is_array( $this->_model_relations[$relationName] ) && isset( $this->_model_relations[$relationName][ $current_cache_id ] )) {
731
				// then remove the current cached item
732
				unset( $this->_model_relations[$relationName][ $current_cache_id ] );
733
				// and cache the newly saved object using it's new ID
734
				$this->_model_relations[$relationName][ $newly_saved_object->ID() ] = $newly_saved_object;
735
				return TRUE;
736
			}
737
		}
738
		return FALSE;
739
	}
740
741
742
743
	/**
744
	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
745
	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
746
	 *
747
	 * @param string $relationName
748
	 * @return EE_Base_Class
749
	 */
750
	public function get_one_from_cache($relationName){
751
		$cached_array_or_object = isset( $this->_model_relations[$relationName] ) ? $this->_model_relations[$relationName] : null;
752
		if(is_array($cached_array_or_object)){
753
			return array_shift($cached_array_or_object);
754
		}else{
755
			return $cached_array_or_object;
756
		}
757
	}
758
759
760
761
	/**
762
	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
763
	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
764
	 *
765
	 * @param string $relationName
766
	 * @throws \EE_Error
767
	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
768
	 */
769
	public function get_all_from_cache($relationName){
770
		$objects = isset( $this->_model_relations[$relationName] ) ? $this->_model_relations[$relationName] : array();
771
		// if the result is not an array, but exists, make it an array
772
		$objects = is_array( $objects ) ? $objects : array( $objects );
773
		//bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
774
		//basically, if this model object was stored in the session, and these cached model objects
775
		//already have IDs, let's make sure they're in their model's entity mapper
776
		//otherwise we will have duplicates next time we call
777
		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
778
		$model = EE_Registry::instance()->load_model( $relationName );
779
		foreach( $objects as $model_object ){
780
			if( $model instanceof EEM_Base && $model_object instanceof EE_Base_Class ){
781
				//ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
782
				if( $model_object->ID() ){
783
					$model->add_to_entity_map( $model_object );
784
				}
785
			}else{
786
				throw new EE_Error(
787
					sprintf(
788
						__(
789
							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
790
							'event_espresso'
791
						),
792
						$relationName,
793
						gettype( $model_object )
794
					)
795
				);
796
			}
797
		}
798
		return $objects;
799
	}
800
801
802
803
	/**
804
	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
805
	 * matching the given query conditions.
806
	 *
807
	 * @param null  $field_to_order_by  What field is being used as the reference point.
808
	 * @param int   $limit              How many objects to return.
809
	 * @param array $query_params       Any additional conditions on the query.
810
	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
811
	 *                                  you can indicate just the columns you want returned
812
	 * @return array|EE_Base_Class[]
813
	 * @throws \EE_Error
814
	 */
815 View Code Duplication
	public function next_x( $field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
816
		$field = empty( $field_to_order_by ) && $this->get_model()->has_primary_key_field()
817
			? $this->get_model()->get_primary_key_field()->get_name()
818
			: $field_to_order_by;
819
		$current_value = ! empty( $field ) ? $this->get( $field ) : null;
820
		if ( empty( $field ) || empty( $current_value ) ) {
821
			return array();
822
		}
823
		return $this->get_model()->next_x( $current_value, $field, $limit, $query_params, $columns_to_select );
824
	}
825
826
827
828
	/**
829
	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
830
	 * matching the given query conditions.
831
	 *
832
	 * @param null  $field_to_order_by  What field is being used as the reference point.
833
	 * @param int   $limit              How many objects to return.
834
	 * @param array $query_params       Any additional conditions on the query.
835
	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
836
	 *                                  you can indicate just the columns you want returned
837
	 * @return array|EE_Base_Class[]
838
	 * @throws \EE_Error
839
	 */
840 View Code Duplication
	public function previous_x( $field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
841
		$field = empty( $field_to_order_by ) && $this->get_model()->has_primary_key_field()
842
			? $this->get_model()->get_primary_key_field()->get_name()
843
			: $field_to_order_by;
844
		$current_value = ! empty( $field ) ? $this->get( $field ) : null;
845
		if ( empty( $field ) || empty( $current_value ) ) {
846
			return array();
847
		}
848
		return $this->get_model()->previous_x( $current_value, $field, $limit, $query_params, $columns_to_select );
849
	}
850
851
852
853
	/**
854
	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
855
	 * matching the given query conditions.
856
	 *
857
	 * @param null  $field_to_order_by  What field is being used as the reference point.
858
	 * @param array $query_params       Any additional conditions on the query.
859
	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
860
	 *                                  you can indicate just the columns you want returned
861
	 * @return array|EE_Base_Class
862
	 * @throws \EE_Error
863
	 */
864 View Code Duplication
	public function next( $field_to_order_by = null, $query_params = array(), $columns_to_select = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
865
		$field = empty( $field_to_order_by ) && $this->get_model()->has_primary_key_field()
866
			? $this->get_model()->get_primary_key_field()->get_name()
867
			: $field_to_order_by;
868
		$current_value = ! empty( $field ) ? $this->get( $field ) : null;
869
		if ( empty( $field ) || empty( $current_value ) ) {
870
			return array();
871
		}
872
		return $this->get_model()->next( $current_value, $field, $query_params, $columns_to_select );
873
	}
874
875
876
877
	/**
878
	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
879
	 * matching the given query conditions.
880
	 *
881
	 * @param null  $field_to_order_by  What field is being used as the reference point.
882
	 * @param array $query_params       Any additional conditions on the query.
883
	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
884
	 *                                  you can indicate just the column you want returned
885
	 * @return array|EE_Base_Class
886
	 * @throws \EE_Error
887
	 */
888 View Code Duplication
	public function previous( $field_to_order_by = null, $query_params = array(), $columns_to_select = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
889
		$field = empty( $field_to_order_by ) && $this->get_model()->has_primary_key_field()
890
			? $this->get_model()->get_primary_key_field()->get_name()
891
			: $field_to_order_by;
892
		$current_value = ! empty( $field ) ? $this->get( $field ) : null;
893
		if ( empty( $field ) || empty( $current_value ) ) {
894
			return array();
895
		}
896
		return $this->get_model()->previous( $current_value, $field, $query_params, $columns_to_select );
897
	}
898
899
900
901
	/**
902
	 * Overrides parent because parent expects old models.
903
	 * This also doesn't do any validation, and won't work for serialized arrays
904
	 *
905
	 * @param string $field_name
906
	 * @param mixed  $field_value_from_db
907
	 * @throws \EE_Error
908
	 */
909
	public function set_from_db($field_name,$field_value_from_db){
910
		$field_obj = $this->get_model()->field_settings_for($field_name);
911
		if ( $field_obj instanceof EE_Model_Field_Base ) {
912
			//you would think the DB has no NULLs for non-null label fields right? wrong!
913
			//eg, a CPT model object could have an entry in the posts table, but no
914
			//entry in the meta table. Meaning that all its columns in the meta table
915
			//are null! yikes! so when we find one like that, use defaults for its meta columns
916
			if($field_value_from_db === NULL ){
917
				if( $field_obj->is_nullable()){
918
					//if the field allows nulls, then let it be null
919
					$field_value = NULL;
920
				}else{
921
					$field_value = $field_obj->get_default_value();
922
				}
923
			}else{
924
				$field_value = $field_obj->prepare_for_set_from_db( $field_value_from_db );
925
			}
926
			$this->_fields[$field_name] = $field_value;
927
			$this->_clear_cached_property( $field_name );
928
		}
929
	}
930
931
932
933
	/**
934
	 * verifies that the specified field is of the correct type
935
	 *
936
	 * @param string $field_name
937
	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
938
	 *                                (in cases where the same property may be used for different outputs
939
	 *                                - i.e. datetime, money etc.)
940
	 * @return mixed
941
	 * @throws \EE_Error
942
	 */
943
	public function get($field_name, $extra_cache_ref = NULL ){
944
		return $this->_get_cached_property( $field_name, FALSE, $extra_cache_ref );
945
	}
946
947
948
	/**
949
	 * This method simply returns the RAW unprocessed value for the given property in this class
950
	 *
951
	 * @param  string $field_name A valid fieldname
952
	 * @return mixed              Whatever the raw value stored on the property is.
953
	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
954
	 */
955
	public function get_raw($field_name) {
956
		$field_settings = $this->get_model()->field_settings_for($field_name);
957
		return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
958
			? $this->_fields[$field_name]->format('U')
959
			: $this->_fields[$field_name];
960
961
	}
962
963
964
965
	/**
966
	 * This is used to return the internal DateTime object used for a field that is a
967
	 * EE_Datetime_Field.
968
	 *
969
	 * @param string $field_name               The field name retrieving the DateTime object.
970
	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
971
	 * @throws \EE_Error
972
	 *                                         an error is set and false returned.  If the field IS an
973
	 *                                         EE_Datetime_Field and but the field value is null, then
974
	 *                                         just null is returned (because that indicates that likely
975
	 *                                         this field is nullable).
976
	 */
977
	public function get_DateTime_object( $field_name ) {
978
		$field_settings = $this->get_model()->field_settings_for( $field_name );
979
980
		if ( ! $field_settings instanceof EE_Datetime_Field ) {
981
			EE_Error::add_error(
982
				sprintf(
983
					__(
984
						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
985
						'event_espresso'
986
					),
987
					$field_name
988
				),
989
				__FILE__,
990
				__FUNCTION__,
991
				__LINE__
992
			);
993
			return false;
994
		}
995
996
		return $this->_fields[$field_name];
997
	}
998
999
1000
1001
	/**
1002
	 * To be used in template to immediately echo out the value, and format it for output.
1003
	 * Eg, should call stripslashes and whatnot before echoing
1004
	 *
1005
	 * @param string $field_name      the name of the field as it appears in the DB
1006
	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1007
	 *                                (in cases where the same property may be used for different outputs
1008
	 *                                - i.e. datetime, money etc.)
1009
	 * @return void
1010
	 * @throws \EE_Error
1011
	 */
1012
	public function e($field_name, $extra_cache_ref = NULL){
1013
		echo $this->get_pretty($field_name, $extra_cache_ref);
1014
	}
1015
1016
1017
1018
	/**
1019
	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1020
	 * can be easily used as the value of form input.
1021
	 *
1022
	 * @param string $field_name
1023
	 * @return void
1024
	 * @throws \EE_Error
1025
	 */
1026
	public function f($field_name){
1027
		$this->e($field_name,'form_input');
1028
	}
1029
1030
1031
1032
	/**
1033
	 * @param string $field_name
1034
	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1035
	 *                                (in cases where the same property may be used for different outputs
1036
	 *                                - i.e. datetime, money etc.)
1037
	 * @return mixed
1038
	 * @throws \EE_Error
1039
	 */
1040
	public function get_pretty($field_name, $extra_cache_ref = NULL){
1041
		return  $this->_get_cached_property( $field_name, TRUE, $extra_cache_ref );
1042
	}
1043
1044
1045
1046
	/**
1047
	 * This simply returns the datetime for the given field name
1048
	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1049
	 * (and the equivalent e_date, e_time, e_datetime).
1050
	 *
1051
	 * @access   protected
1052
	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1053
	 * @param string   $dt_frmt      valid datetime format used for date
1054
	 *                               (if '' then we just use the default on the field,
1055
	 *                               if NULL we use the last-used format)
1056
	 * @param string   $tm_frmt      Same as above except this is for time format
1057
	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1058
	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1059
	 * @return void|string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1060
	 *                               if field is not a valid dtt field, or void if echoing
1061
	 * @throws \EE_Error
1062
	 */
1063
	protected function _get_datetime( $field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false ) {
1064
1065
		$in_dt_frmt = empty($dt_frmt) ? $this->_dt_frmt :  $dt_frmt;
1066
		$in_tm_frmt = empty($tm_frmt) ? $this->_tm_frmt : $tm_frmt;
1067
1068
		//validate field for datetime and returns field settings if valid.
1069
		$field = $this->_get_dtt_field_settings( $field_name );
1070
1071
		//clear cached property if either formats are not null.
1072
		if( $dt_frmt !== null || $tm_frmt !== null ) {
1073
			$this->_clear_cached_property( $field_name );
1074
			//reset format properties because they are used in get()
1075
			$this->_dt_frmt = $in_dt_frmt;
1076
			$this->_tm_frmt = $in_tm_frmt;
1077
		}
1078
		if ( $echo ) {
1079
			$field->set_pretty_date_format( $in_dt_frmt );
1080
		} else {
1081
			$field->set_date_format( $in_dt_frmt );
1082
		}
1083
		if ( $echo ) {
1084
			$field->set_pretty_time_format( $in_tm_frmt );
1085
		} else {
1086
			$field->set_time_format( $in_tm_frmt );
1087
		}
1088
		//set timezone in field object
1089
		$field->set_timezone( $this->_timezone );
1090
1091
		//set the output returned
1092
		switch ( $date_or_time ) {
1093
1094
			case 'D' :
1095
				$field->set_date_time_output('date');
1096
				break;
1097
1098
			case 'T' :
1099
				$field->set_date_time_output('time');
1100
				break;
1101
1102
			default :
1103
				$field->set_date_time_output();
1104
		}
1105
1106
1107
		if ( $echo ) {
1108
			$this->e( $field_name, $date_or_time );
1109
			return '';
1110
		 }
1111
		return $this->get( $field_name, $date_or_time );
1112
	}
1113
1114
1115
1116
	/**
1117
	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the other echoes the pretty value for dtt)
1118
	 *
1119
	 * @param  string $field_name name of model object datetime field holding the value
1120
	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1121
	 * @return string            datetime value formatted
1122
	 * @throws \EE_Error
1123
	 */
1124
	public function get_date( $field_name, $format = NULL ) {
1125
		return $this->_get_datetime( $field_name, $format, NULL, 'D' );
1126
	}
1127
1128
1129
1130
	/**
1131
	 * @param      $field_name
1132
	 * @param null $format
1133
	 * @throws \EE_Error
1134
	 */
1135
	public function e_date( $field_name, $format = NULL ) {
1136
		$this->_get_datetime( $field_name, $format, NULL, 'D', TRUE );
1137
	}
1138
1139
1140
1141
	/**
1142
	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the other echoes the pretty value for dtt)
1143
	 *
1144
	 * @param  string $field_name name of model object datetime field holding the value
1145
	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1146
	 * @return string             datetime value formatted
1147
	 * @throws \EE_Error
1148
	 */
1149
	public function get_time( $field_name, $format = NULL ) {
1150
		return $this->_get_datetime( $field_name, NULL, $format, 'T' );
1151
	}
1152
1153
1154
1155
	/**
1156
	 * @param      $field_name
1157
	 * @param null $format
1158
	 * @throws \EE_Error
1159
	 */
1160
	public function e_time( $field_name, $format = NULL ) {
1161
		$this->_get_datetime( $field_name, NULL, $format, 'T', TRUE );
1162
	}
1163
1164
1165
1166
	/**
1167
	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the other echoes the pretty value for dtt)
1168
	 *
1169
	 * @param  string $field_name name of model object datetime field holding the value
1170
	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1171
	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1172
	 * @return string             datetime value formatted
1173
	 * @throws \EE_Error
1174
	 */
1175
	public function get_datetime( $field_name, $dt_frmt = NULL, $tm_frmt = NULL ) {
1176
		return $this->_get_datetime( $field_name, $dt_frmt, $tm_frmt );
1177
	}
1178
1179
1180
1181
	/**
1182
	 * @param      $field_name
1183
	 * @param null $dt_frmt
1184
	 * @param null $tm_frmt
1185
	 * @throws \EE_Error
1186
	 */
1187
	public function e_datetime( $field_name, $dt_frmt = NULL, $tm_frmt = NULL ) {
1188
		$this->_get_datetime( $field_name, $dt_frmt, $tm_frmt, NULL, TRUE);
1189
	}
1190
1191
1192
1193
	/**
1194
	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1195
	 *
1196
	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1197
	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format on the object will be used.
1198
	 * @return string Date and time string in set locale or false if no field exists for the given
1199
	 * @throws \EE_Error
1200
	 *                           field name.
1201
	 */
1202
	public function get_i18n_datetime( $field_name, $format = NULL ) {
1203
		$format = empty( $format ) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1204
		return date_i18n(
1205
			$format,
1206
			EEH_DTT_Helper::get_timestamp_with_offset( $this->get_raw( $field_name ), $this->_timezone )
1207
		);
1208
	}
1209
1210
1211
1212
1213
	/**
1214
	 * This method validates whether the given field name is a valid field on the model object as well as it is of a type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is thrown.
1215
	 * @param  string $field_name The field name being checked
1216
	 * @throws EE_Error
1217
	 * @return EE_Datetime_Field
1218
	 */
1219
	protected function _get_dtt_field_settings( $field_name ) {
1220
		$field = $this->get_model()->field_settings_for($field_name);
1221
1222
		//check if field is dtt
1223
		if ( $field instanceof EE_Datetime_Field ) {
1224
			return $field;
1225
		} else {
1226
			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', 'event_espresso'), $field_name, self::_get_model_classname( get_class($this) ) ) );
1227
		}
1228
	}
1229
1230
1231
1232
1233
	/**
1234
	 * NOTE ABOUT BELOW:
1235
	 * These convenience date and time setters are for setting date and time independently.  In other words you might want to change the time on a datetime_field but leave the date the same (or vice versa).
1236
	 *
1237
	 * IF on the other hand you want to set both date and time at the same time, you can just use the models default set($fieldname,$value) method and make sure you send the entire datetime value for setting.
1238
	 */
1239
	/**
1240
	 * sets the time on a datetime property
1241
	 *
1242
	 * @access protected
1243
	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1244
	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1245
	 * @throws \EE_Error
1246
	 */
1247
	protected function _set_time_for( $time, $fieldname ) {
1248
		$this->_set_date_time( 'T', $time, $fieldname );
1249
	}
1250
1251
1252
1253
	/**
1254
	 * sets the date on a datetime property
1255
	 *
1256
	 * @access protected
1257
	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1258
	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1259
	 * @throws \EE_Error
1260
	 */
1261
	protected function _set_date_for( $date, $fieldname ) {
1262
		$this->_set_date_time( 'D', $date, $fieldname );
1263
	}
1264
1265
1266
1267
	/**
1268
	 * This takes care of setting a date or time independently on a given model object property. This method also verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1269
	 *
1270
	 * @access protected
1271
	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1272
	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1273
	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a EE_Datetime_Field property)
1274
	 * @throws \EE_Error
1275
	 */
1276
	protected function _set_date_time( $what = 'T', $datetime_value, $fieldname ) {
1277
		$field = $this->_get_dtt_field_settings( $fieldname );
1278
		$field->set_timezone( $this->_timezone );
1279
		$field->set_date_format( $this->_dt_frmt );
1280
		$field->set_time_format( $this->_tm_frmt );
1281
		switch ( $what ) {
1282
			case 'T' :
1283
				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1284
					$datetime_value,
1285
					$this->_fields[ $fieldname ]
1286
				);
1287
				break;
1288
			case 'D' :
1289
				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1290
					$datetime_value,
1291
					$this->_fields[ $fieldname ]
1292
				);
1293
				break;
1294
			case 'B' :
1295
				$this->_fields[ $fieldname ] = $field->prepare_for_set( $datetime_value );
0 ignored issues
show
Bug introduced by
It seems like $datetime_value defined by parameter $datetime_value on line 1276 can also be of type object<DateTime>; however, EE_Datetime_Field::prepare_for_set() does only seem to accept string|integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1296
				break;
1297
		}
1298
		$this->_clear_cached_property($fieldname);
1299
	}
1300
1301
1302
1303
	/**
1304
	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different than the timezone set for the website.
1305
	 *
1306
	 * NOTE, this currently only works well with methods that return values.  If you use it with methods that echo values the $_timestamp property may not get reset to its original value and that could lead to some unexpected results!
1307
	 *
1308
	 * @access public
1309
	 * @param string               $field_name This is the name of the field on the object that contains the date/time value being returned.
1310
	 * @param string               $callback   must match a valid method in this class (defaults to get_datetime)
1311
	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1312
	 * @param string               $prepend    You can include something to prepend on the timestamp
1313
	 * @param string               $append     You can include something to append on the timestamp
1314
	 * @throws EE_Error
1315
	 * @return string timestamp
1316
	 */
1317
	public function display_in_my_timezone( $field_name, $callback = 'get_datetime', $args = NULL, $prepend = '', $append = '' ) {
1318
		$timezone = EEH_DTT_Helper::get_timezone();
1319
		if ( $timezone === $this->_timezone ) {
1320
			return '';
1321
		}
1322
		$original_timezone = $this->_timezone;
1323
		$this->set_timezone( $timezone );
1324
1325
		$fn = (array) $field_name;
1326
		$args = array_merge( $fn, (array) $args );
1327 View Code Duplication
		if ( ! method_exists( $this, $callback ) ) {
1328
			throw new EE_Error(
1329
				sprintf(
1330
					__(
1331
						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1332
						'event_espresso'
1333
					),
1334
					$callback
1335
				)
1336
			);
1337
		}
1338
		$args = (array) $args;
1339
		$return =  $prepend . call_user_func_array( array( $this, $callback ), $args ) . $append;
1340
1341
		$this->set_timezone( $original_timezone );
1342
		return $return;
1343
	}
1344
1345
1346
1347
	/**
1348
	 * Deletes this model object.
1349
	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should override
1350
	 * `EE_Base_Class::_delete` NOT this class.
1351
	 *
1352
	 * @return boolean | int
1353
	 * @throws \EE_Error
1354
	 */
1355
	public function delete(){
1356
		/**
1357
		 * Called just before the `EE_Base_Class::_delete` method call.
1358
		 * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1359
		 * should be aware that `_delete` may not always result in a permanent delete.  For example, `EE_Soft_Delete_Base_Class::_delete`
1360
		 * soft deletes (trash) the object and does not permanently delete it.
1361
		 *
1362
		 * @param EE_Base_Class $model_object about to be 'deleted'
1363
		 */
1364
		do_action( 'AHEE__EE_Base_Class__delete__before', $this );
1365
		$result = $this->_delete();
1366
		/**
1367
		 * Called just after the `EE_Base_Class::_delete` method call.
1368
		 * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1369
		 * should be aware that `_delete` may not always result in a permanent delete.  For example `EE_Soft_Base_Class::_delete`
1370
		 * soft deletes (trash) the object and does not permanently delete it.
1371
		 * @param EE_Base_Class $model_object that was just 'deleted'
1372
		 * @param boolean $result
1373
		 */
1374
		do_action( 'AHEE__EE_Base_Class__delete__end', $this, $result );
1375
		return $result;
1376
	}
1377
1378
1379
1380
	/**
1381
	 * Calls the specific delete method for the instantiated class.
1382
	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override default
1383
	 * functionality for "delete" (which is to call `permanently_delete`) should override this method NOT `EE_Base_Class::delete`
1384
	 *
1385
	 * @return bool|int
1386
	 * @throws \EE_Error
1387
	 */
1388
	protected function _delete() {
1389
		return $this->delete_permanently();
1390
	}
1391
1392
1393
1394
	/**
1395
	 * Deletes this model object permanently from db (but keep in mind related models my block the delete and return an error)
1396
	 *
1397
	 * @return bool | int
1398
	 * @throws \EE_Error
1399
	 */
1400
	public function delete_permanently(){
1401
		/**
1402
		 * Called just before HARD deleting a model object
1403
		 *
1404
		 * @param EE_Base_Class $model_object about to be 'deleted'
1405
		 */
1406
		do_action( 'AHEE__EE_Base_Class__delete_permanently__before', $this );
1407
		$model=$this->get_model();
1408
		$result=$model->delete_permanently_by_ID($this->ID());
1409
		$this->refresh_cache_of_related_objects();
1410
		/**
1411
		 * Called just after HARD deleting a model object
1412
		 * @param EE_Base_Class $model_object that was just 'deleted'
1413
		 * @param boolean $result
1414
		 */
1415
		do_action( 'AHEE__EE_Base_Class__delete_permanently__end', $this, $result );
1416
		return $result;
1417
	}
1418
1419
1420
1421
	/**
1422
	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1423
	 * related model objects
1424
	 *
1425
	 * @throws \EE_Error
1426
	 */
1427
        public function refresh_cache_of_related_objects() {
1428
            foreach( $this->get_model()->relation_settings() as $relation_name => $relation_obj ) {
1429
                if( ! empty( $this->_model_relations[ $relation_name ] ) ) {
1430
                    $related_objects = $this->_model_relations[ $relation_name ];
1431
                    if( $relation_obj instanceof EE_Belongs_To_Relation ) {
1432
                        //this relation only stores a single model object, not an array
1433
                        //but let's make it consistent
1434
                        $related_objects = array( $related_objects );
1435
                    }
1436
                    foreach( $related_objects as $related_object ) {
1437
                        //only refresh their cache if they're in memory
1438
                        if( $related_object instanceof EE_Base_Class ) {
1439
							$related_object->clear_cache( $this->get_model()->get_this_model_name(), $this );
1440
                        }
1441
                    }
1442
                }
1443
            }
1444
        }
1445
1446
1447
1448
	/**
1449
	 *        Saves this object to the database. An array may be supplied to set some values on this
1450
	 * object just before saving.
1451
	 *
1452
	 * @access public
1453
	 * @param array $set_cols_n_values 	keys are field names, values are their new values,
1454
	 * 		if provided during the save() method (often client code will change the fields' values before calling save)
1455
	 * @throws \EE_Error
1456
	 * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1457
	 * isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1458
	 */
1459
	public function save($set_cols_n_values=array()) {
1460
		/**
1461
		 * Filters the fields we're about to save on the model object
1462
		 *
1463
		 * @param array $set_cols_n_values
1464
		 * @param EE_Base_Class $model_object
1465
		 */
1466
		$set_cols_n_values = (array)apply_filters( 'FHEE__EE_Base_Class__save__set_cols_n_values', $set_cols_n_values, $this  );
1467
		//set attributes as provided in $set_cols_n_values
1468
		foreach($set_cols_n_values as $column=>$value){
1469
			$this->set($column,$value);
1470
		}
1471
		/**
1472
		 * Saving a model object.
1473
		 *
1474
		 * Before we perform a save, this action is fired.
1475
		 * @param EE_Base_Class $model_object the model object about to be saved.
1476
		 */
1477
		do_action( 'AHEE__EE_Base_Class__save__begin', $this );
1478
		if( ! $this->allow_persist() ) {
1479
			return 0;
1480
		}
1481
		//now get current attribute values
1482
		$save_cols_n_values = $this->_fields;
1483
		//if the object already has an ID, update it. Otherwise, insert it
1484
		//also: change the assumption about values passed to the model NOT being prepare dby the model object. They have been
1485
		$old_assumption_concerning_value_preparation = $this->get_model()->get_assumption_concerning_values_already_prepared_by_model_object();
1486
		$this->get_model()->assume_values_already_prepared_by_model_object(true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1487
		//does this model have an autoincrement PK?
1488
		if($this->get_model()->has_primary_key_field()){
1489
			if($this->get_model()->get_primary_key_field()->is_auto_increment()){
1490
				//ok check if it's set, if so: update; if not, insert
1491
				if ( ! empty( $save_cols_n_values[self::_get_primary_key_name( get_class($this) )] ) ){
1492
					$results = $this->get_model()->update_by_ID ( $save_cols_n_values, $this->ID() );
1493
				} else {
1494
					unset($save_cols_n_values[self::_get_primary_key_name( get_class( $this) )]);
1495
					$results = $this->get_model()->insert( $save_cols_n_values );
1496
					if($results){
1497
						//if successful, set the primary key
1498
						//but don't use the normal SET method, because it will check if
1499
						//an item with the same ID exists in the mapper & db, then
1500
						//will find it in the db (because we just added it) and THAT object
1501
						//will get added to the mapper before we can add this one!
1502
						//but if we just avoid using the SET method, all that headache can be avoided
1503
						$pk_field_name =self::_get_primary_key_name( get_class($this));
1504
						$this->_fields[$pk_field_name] = $results;
1505
						$this->_clear_cached_property($pk_field_name);
1506
						$this->get_model()->add_to_entity_map( $this );
1507
						$this->_update_cached_related_model_objs_fks();
1508
					}
1509
				}
1510
			}else{//PK is NOT auto-increment
1511
				//so check if one like it already exists in the db
1512
				if( $this->get_model()->exists_by_ID( $this->ID() ) ){
1513 View Code Duplication
					if( WP_DEBUG && ! $this->in_entity_map() ){
1514
						throw new EE_Error(
1515
							sprintf(
1516
								__( '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', 'event_espresso' ),
1517
								get_class($this),
1518
								get_class( $this->get_model() ) . '::instance()->add_to_entity_map()',
1519
								get_class( $this->get_model() ) . '::instance()->get_one_by_ID()',
1520
								'<br />'
1521
							)
1522
						);
1523
					}
1524
					$results = $this->get_model()->update_by_ID($save_cols_n_values, $this->ID());
1525
				}else{
1526
					$results = $this->get_model()->insert($save_cols_n_values);
1527
					$this->_update_cached_related_model_objs_fks();
1528
				}
1529
			}
1530
		}else{//there is NO primary key
1531
			$already_in_db = false;
1532
			foreach($this->get_model()->unique_indexes() as $index){
1533
				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1534
				if($this->get_model()->exists(array($uniqueness_where_params))){
1535
					$already_in_db = true;
1536
				}
1537
			}
1538
			if( $already_in_db ){
1539
				$combined_pk_fields_n_values = array_intersect_key( $save_cols_n_values, $this->get_model()->get_combined_primary_key_fields() );
1540
				$results = $this->get_model()->update( $save_cols_n_values,$combined_pk_fields_n_values );
1541
			}else{
1542
				$results = $this->get_model()->insert( $save_cols_n_values );
1543
			}
1544
		}
1545
		//restore the old assumption about values being prepared by the model object
1546
		$this->get_model()->assume_values_already_prepared_by_model_object($old_assumption_concerning_value_preparation);
1547
1548
		/**
1549
		 * After saving the model object this action is called
1550
		 *
1551
		 * @param EE_Base_Class $model_object which was just saved
1552
		 * @param boolean|int $results if it were updated, TRUE or FALSE; if it were newly inserted
1553
		 * the new ID (or 0 if an error occurred and it wasn't updated)
1554
		 */
1555
		do_action( 'AHEE__EE_Base_Class__save__end', $this, $results );
1556
		return $results;
1557
	}
1558
1559
1560
1561
	/**
1562
	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1563
	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB is consistent)
1564
	 * Especially useful in case we JUST added this model object ot the database
1565
	 * and we want to let its cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't 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 transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether or not they exist in the DB (if they do, their DB records will be automatically updated)
1566
	 *
1567
	 * @return void
1568
	 * @throws \EE_Error
1569
	 */
1570
	protected function _update_cached_related_model_objs_fks(){
1571
		foreach( $this->get_model()->relation_settings() as $relation_name => $relation_obj ){
1572
			if( $relation_obj instanceof EE_Has_Many_Relation ){
1573
				foreach( $this->get_all_from_cache( $relation_name ) as $related_model_obj_in_cache) {
1574
					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1575
						$this->get_model()->get_this_model_name()
1576
					);
1577
					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID() );
1578
					if( $related_model_obj_in_cache->ID() ){
1579
						$related_model_obj_in_cache->save();
1580
					}
1581
				}
1582
			}
1583
		}
1584
	}
1585
1586
1587
1588
	/**
1589
	 * Saves this model object and its NEW cached relations to the database.
1590
	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1591
	 * In order for that to work, we would need to mark model objects as dirty/clean...
1592
	 * because otherwise, there's a potential for infinite looping of saving
1593
	 * Saves the cached related model objects, and ensures the relation between them
1594
	 * and this object and properly setup
1595
	 *
1596
	 * @return int ID of new model object on save; 0 on failure+
1597
	 * @throws \EE_Error
1598
	 */
1599
	public function save_new_cached_related_model_objs(){
1600
		//make sure this has been saved
1601
		if( ! $this->ID()){
1602
			$id = $this->save();
1603
		}else{
1604
			$id = $this->ID();
1605
		}
1606
		//now save all the NEW cached model objects  (ie they don't exist in the DB)
1607
		foreach($this->get_model()->relation_settings() as $relationName => $relationObj){
1608
1609
1610
			if($this->_model_relations[$relationName]){
1611
				//is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1612
				//or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1613
				if($relationObj instanceof EE_Belongs_To_Relation){
1614
					//add a relation to that relation type (which saves the appropriate thing in the process)
1615
					//but ONLY if it DOES NOT exist in the DB
1616
					/* @var $related_model_obj EE_Base_Class */
1617
					$related_model_obj = $this->_model_relations[$relationName];
1618
//					if( ! $related_model_obj->ID()){
1619
						$this->_add_relation_to($related_model_obj, $relationName);
1620
						$related_model_obj->save_new_cached_related_model_objs();
1621
//					}
1622
				}else{
1623
					foreach($this->_model_relations[$relationName] as $related_model_obj){
1624
						//add a relation to that relation type (which saves the appropriate thing in the process)
1625
						//but ONLY if it DOES NOT exist in the DB
1626
//						if( ! $related_model_obj->ID()){
1627
							$this->_add_relation_to($related_model_obj, $relationName);
1628
							$related_model_obj->save_new_cached_related_model_objs();
1629
//						}
1630
					}
1631
				}
1632
			}
1633
		}
1634
1635
		return $id;
1636
	}
1637
1638
1639
	/**
1640
	 * for getting a model while instantiated.
1641
	 * @return \EEM_Base | \EEM_CPT_Base
1642
	 */
1643
	public function get_model() {
1644
		$modelName = self::_get_model_classname( get_class($this) );
1645
		return self::_get_model_instance_with_name($modelName, $this->_timezone );
1646
	}
1647
1648
1649
1650
	/**
1651
	 * @param $props_n_values
1652
	 * @param $classname
1653
	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
1654
	 * @throws \EE_Error
1655
	 */
1656
	protected static function _get_object_from_entity_mapper($props_n_values, $classname){
1657
		//TODO: will not work for Term_Relationships because they have no PK!
1658
		$primary_id_ref = self::_get_primary_key_name( $classname );
1659 View Code Duplication
		if ( array_key_exists( $primary_id_ref, $props_n_values ) && !empty( $props_n_values[$primary_id_ref] ) ) {
1660
			$id = $props_n_values[$primary_id_ref];
1661
			return self::_get_model($classname)->get_from_entity_map($id);
1662
		}
1663
		return false;
1664
	}
1665
1666
1667
1668
	/**
1669
	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for the primary key (if present in incoming values).
1670
	 * If there is a key in the incoming array that matches the 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 we return false.
1671
	 *
1672
	 * @param  array  $props_n_values   incoming array of properties and their values
1673
	 * @param  string $classname        the classname of the child class
1674
	 * @param null    $timezone
1675
	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
1676
	 *                                  date_format and the second value is the time format
1677
	 * @return mixed (EE_Base_Class|bool)
1678
	 * @throws \EE_Error
1679
	 */
1680
	protected static function _check_for_object( $props_n_values, $classname, $timezone = NULL, $date_formats = array() ) {
1681
		$existing = null;
1682
		if ( self::_get_model( $classname )->has_primary_key_field() ) {
1683
			$primary_id_ref = self::_get_primary_key_name( $classname );
1684 View Code Duplication
			if ( array_key_exists( $primary_id_ref, $props_n_values )
1685
			     && ! empty( $props_n_values[ $primary_id_ref ] )
1686
			) {
1687
				$existing = self::_get_model( $classname, $timezone )->get_one_by_ID(
1688
					$props_n_values[ $primary_id_ref ]
1689
				);
1690
			}
1691
		} elseif ( self::_get_model( $classname, $timezone )->has_all_combined_primary_key_fields( $props_n_values ) ) {
1692
			//no primary key on this model, but there's still a matching item in the DB
1693
			$existing = self::_get_model( $classname, $timezone )->get_one_by_ID(
1694
				self::_get_model( $classname, $timezone )->get_index_primary_key_string( $props_n_values )
1695
			);
1696
		}
1697
		if ( $existing ) {
1698
1699
			//set date formats if present before setting values
1700
			if ( ! empty( $date_formats ) && is_array( $date_formats ) ) {
1701
				$existing->set_date_format( $date_formats[0] );
1702
				$existing->set_time_format( $date_formats[1] );
1703
			} else {
1704
				//set default formats for date and time
1705
				$existing->set_date_format( get_option( 'date_format' ) );
1706
				$existing->set_time_format( get_option( 'time_format' ) );
1707
			}
1708
1709
			foreach ( $props_n_values as $property => $field_value ) {
1710
				$existing->set( $property, $field_value );
1711
			}
1712
			return $existing;
1713
		} else {
1714
			return FALSE;
1715
		}
1716
	}
1717
1718
1719
1720
	/**
1721
	 * Gets the EEM_*_Model for this class
1722
	 * @access public now, as this is more convenient
1723
	 * @param      $classname
1724
	 * @param null $timezone
1725
	 * @throws EE_Error
1726
	 * @return EEM_Base
1727
	 */
1728
	protected static function  _get_model( $classname, $timezone = NULL ){
1729
		//find model for this class
1730
		if( ! $classname ){
1731
			throw new EE_Error(
1732
				sprintf(
1733
					__(
1734
						"What were you thinking calling _get_model(%s)?? You need to specify the class name",
1735
						"event_espresso"
1736
					),
1737
					$classname
1738
				)
1739
			);
1740
		}
1741
		$modelName=self::_get_model_classname($classname);
1742
		return self::_get_model_instance_with_name($modelName, $timezone );
1743
	}
1744
1745
1746
1747
	/**
1748
	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
1749
	 * @param string $model_classname
1750
	 * @param null   $timezone
1751
	 * @return EEM_Base
1752
	 */
1753
	protected static function _get_model_instance_with_name($model_classname, $timezone = NULL){
1754
		$model_classname = str_replace( 'EEM_', '', $model_classname );
1755
		$model = EE_Registry::instance()->load_model( $model_classname );
1756
		$model->set_timezone( $timezone );
1757
		return $model;
1758
	}
1759
1760
1761
1762
	/**
1763
	 * If a model name is provided (eg Registration), gets the model classname for that model.
1764
	 * Also works if a model class's classname is provided (eg EE_Registration).
1765
	 * @param null $model_name
1766
	 * @return string like EEM_Attendee
1767
	 */
1768
	private static function _get_model_classname( $model_name = null){
1769
		if(strpos($model_name,"EE_")===0){
1770
			$model_classname=str_replace("EE_","EEM_",$model_name);
1771
		}else{
1772
			$model_classname = "EEM_".$model_name;
1773
		}
1774
		return $model_classname;
1775
	}
1776
1777
1778
1779
	/**
1780
	 * returns the name of the primary key attribute
1781
	 * @param null $classname
1782
	 * @throws EE_Error
1783
	 * @return string
1784
	 */
1785
	protected static function _get_primary_key_name( $classname = NULL ){
1786
		if( ! $classname){
1787
			throw new EE_Error(
1788
				sprintf(
1789
					__( "What were you thinking calling _get_primary_key_name(%s)", "event_espresso" ),
1790
					$classname
1791
				)
1792
			);
1793
		}
1794
		return self::_get_model( $classname )->get_primary_key_field()->get_name();
1795
	}
1796
1797
1798
1799
	/**
1800
	 * Gets the value of the primary key.
1801
	 * If the object hasn't yet been saved, it should be whatever the model field's default was
1802
	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value is.
1803
	 * Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
1804
	 *
1805
	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
0 ignored issues
show
Documentation introduced by
The doc-type mixed, could not be parsed: Expected "|" or "end of type", but got "," at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1806
	 * @throws \EE_Error
1807
	 */
1808
	public function ID(){
1809
		//now that we know the name of the variable, use a variable variable to get its value and return its
1810
		if( $this->get_model()->has_primary_key_field() ) {
1811
			return $this->_fields[ self::_get_primary_key_name( get_class($this) ) ];
1812
		}else{
1813
			return $this->get_model()->get_index_primary_key_string( $this->_fields );
1814
		}
1815
	}
1816
1817
1818
1819
	/**
1820
	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current model is related
1821
	 * to a group of events, the $relationName should be 'Event', and should be a key in the EE Model's $_model_relations array.
1822
	 * If this model object doesn't exist in the DB, just caches the related thing
1823
	 * @param mixed  $otherObjectModelObjectOrID EE_Base_Class or the ID of the other object
1824
	 * @param string $relationName               eg 'Events','Question',etc.
1825
	 *                                           an attendee to a group, you also want to specify which role they will have in that group. So you would use this parameter to specify array('role-column-name'=>'role-id')
1826
	 * @param array  $extra_join_model_fields_n_values                You can optionally include an array of key=>value pairs that allow you to further constrict the relation to being added.  However, keep in mind that the columns (keys) given must match a column on the JOIN table and currently only the HABTM models accept these additional conditions.  Also remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is created in the join table.
1827
	 * @param null   $cache_id
1828
	 * @throws EE_Error
1829
	 * @return EE_Base_Class the object the relation was added to
1830
	 */
1831
	public function _add_relation_to( $otherObjectModelObjectOrID,$relationName, $extra_join_model_fields_n_values = array(), $cache_id = NULL ){
1832
		//if this thing exists in the DB, save the relation to the DB
1833
		if( $this->ID() ){
1834
			$otherObject = $this->get_model()->add_relationship_to( $this, $otherObjectModelObjectOrID, $relationName, $extra_join_model_fields_n_values );
1835
			//clear cache so future get_many_related and get_first_related() return new results.
1836
			$this->clear_cache( $relationName, $otherObject, TRUE );
1837
                        if( $otherObject instanceof EE_Base_Class ) {
1838
                            $otherObject->clear_cache( $this->get_model()->get_this_model_name(), $this );
1839
                        }
1840 View Code Duplication
		} else {
1841
			//this thing doesn't exist in the DB,  so just cache it
1842
			if( ! $otherObjectModelObjectOrID instanceof EE_Base_Class){
1843
				throw new EE_Error( sprintf(
1844
					__( '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', 'event_espresso' ),
1845
					$otherObjectModelObjectOrID,
1846
					get_class( $this )
1847
				));
1848
			} else {
1849
				$otherObject = $otherObjectModelObjectOrID;
1850
			}
1851
			$this->cache( $relationName, $otherObjectModelObjectOrID, $cache_id );
1852
		}
1853 View Code Duplication
                if( $otherObject instanceof EE_Base_Class ) {
1854
                    //fix the reciprocal relation too
1855
                    if( $otherObject->ID() ) {
1856
                            //its saved so assumed relations exist in the DB, so we can just
1857
                            //clear the cache so future queries use the updated info in the DB
1858
                            $otherObject->clear_cache( $this->get_model()->get_this_model_name(), null, true );
1859
                    } else {
1860
1861
                            //it's not saved, so it caches relations like this
1862
                            $otherObject->cache( $this->get_model()->get_this_model_name(), $this );
1863
                    }
1864
                }
1865
		return $otherObject;
1866
	}
1867
1868
1869
1870
	/**
1871
	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current model is related
1872
	 * to a group of events, the $relationName should be 'Events', and should be a key in the EE Model's $_model_relations array.
1873
	 * If this model object doesn't exist in the DB, just removes the related thing from the cache
1874
	 *
1875
	 * @param mixed  $otherObjectModelObjectOrID
1876
	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved to the DB yet
1877
	 * @param string $relationName
1878
	 * @param array  $where_query
1879
	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the relation to being added.
1880
	 *                However, keep in mind that the columns (keys) given must match a column on the JOIN table
1881
	 *                and currently only the HABTM models accept these additional conditions.
1882
	 *                Also remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is created in the join table.
1883
	 * @return EE_Base_Class the relation was removed from
1884
	 * @throws \EE_Error
1885
	 */
1886
	public function _remove_relation_to($otherObjectModelObjectOrID,$relationName, $where_query = array() ){
1887
		if ( $this->ID() ) {
1888
			//if this exists in the DB, save the relation change to the DB too
1889
			$otherObject = $this->get_model()->remove_relationship_to( $this, $otherObjectModelObjectOrID, $relationName, $where_query );
1890
			$this->clear_cache( $relationName, $otherObject );
1891
		} else {
1892
			//this doesn't exist in the DB, just remove it from the cache
1893
			$otherObject = $this->clear_cache( $relationName, $otherObjectModelObjectOrID );
1894
		}
1895
                if( $otherObject instanceof EE_Base_Class ) {
1896
                    $otherObject->clear_cache( $this->get_model()->get_this_model_name(), $this );
1897
                }
1898
		return $otherObject;
1899
	}
1900
1901
1902
1903
	/**
1904
	 * Removes ALL the related things for the $relationName.
1905
	 *
1906
	 * @param string $relationName
1907
	 * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
1908
	 * @return EE_Base_Class
1909
	 * @throws \EE_Error
1910
	 */
1911
	public function _remove_relations($relationName,$where_query_params = array()){
1912
		if ( $this->ID() ) {
1913
			//if this exists in the DB, save the relation change to the DB too
1914
			$otherObjects = $this->get_model()->remove_relations( $this, $relationName, $where_query_params );
1915
			$this->clear_cache( $relationName, null, true );
1916
		} else {
1917
			//this doesn't exist in the DB, just remove it from the cache
1918
			$otherObjects = $this->clear_cache( $relationName, null, true );
1919
		}
1920
                if( is_array( $otherObjects ) ) {
1921
                    foreach ( $otherObjects as $otherObject ) {
1922
                            $otherObject->clear_cache( $this->get_model()->get_this_model_name(), $this );
1923
                    }
1924
                }
1925
		return $otherObjects;
1926
	}
1927
1928
1929
1930
	/**
1931
	 * Gets all the related model objects of the specified type. Eg, if the current class if
1932
	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
1933
	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
1934
	 * because we want to get even deleted items etc.
1935
	 *
1936
	 * @param string $relationName key in the model's _model_relations array
1937
	 * @param array  $query_params like EEM_Base::get_all
1938
	 * @return EE_Base_Class[] Results not necessarily indexed by IDs, because some results might not have primary keys
1939
	 * @throws \EE_Error
1940
	 *                             or might not be saved yet. Consider using EEM_Base::get_IDs() on these results if you want IDs
1941
	 */
1942
	public function get_many_related($relationName,$query_params = array()){
1943
		if($this->ID()){
1944
			//this exists in the DB, so get the related things from either the cache or the DB
1945
			//if there are query parameters, forget about caching the related model objects.
1946
			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...
1947
				$related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
1948 View Code Duplication
			}else{
1949
				//did we already cache the result of this query?
1950
				$cached_results = $this->get_all_from_cache($relationName);
1951
				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...
1952
					$related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
1953
					//if no query parameters were passed, then we got all the related model objects
1954
					//for that relation. We can cache them then.
1955
					foreach($related_model_objects as $related_model_object){
1956
						$this->cache($relationName, $related_model_object);
1957
					}
1958
				}else{
1959
					$related_model_objects = $cached_results;
1960
				}
1961
			}
1962
		}else{
1963
			//this doesn't exist in the DB, so just get the related things from the cache
1964
			$related_model_objects = $this->get_all_from_cache($relationName);
1965
		}
1966
		return $related_model_objects;
1967
	}
1968
1969
1970
1971
	/**
1972
	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
1973
	 * unless otherwise specified in the $query_params
1974
	 * @param string 	$relation_name model_name like 'Event', or 'Registration'
1975
	 * @param array  	$query_params   like EEM_Base::get_all's
1976
	 * @param string 	$field_to_count name of field to count by. By default, uses primary key
1977
	 * @param bool   	$distinct       if we want to only count the distinct values for the column then you can trigger that by the setting $distinct to TRUE;
1978
	 * @return int
1979
	 */
1980
	public function count_related($relation_name, $query_params =array(),$field_to_count = NULL, $distinct = FALSE){
1981
		return $this->get_model()->count_related($this,$relation_name,$query_params,$field_to_count,$distinct);
1982
	}
1983
1984
1985
1986
	/**
1987
	 * Instead of getting the related model objects, simply sums up the values of the specified field.
1988
	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
1989
	 * @param string 	$relation_name model_name like 'Event', or 'Registration'
1990
	 * @param array  	$query_params like EEM_Base::get_all's
1991
	 * @param string 	$field_to_sum name of field to count by.
1992
	 * 						By default, uses primary key (which doesn't make much sense, so you should probably change it)
1993
	 * @return int
1994
	 */
1995
	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null){
1996
		return $this->get_model()->sum_related($this, $relation_name, $query_params, $field_to_sum);
1997
	}
1998
1999
2000
2001
	/**
2002
	 * Gets the first (ie, one) related model object of the specified type.
2003
	 *
2004
	 * @param string $relationName key in the model's _model_relations array
2005
	 * @param array  $query_params like EEM_Base::get_all
2006
	 * @return EE_Base_Class (not an array, a single object)
2007
	 * @throws \EE_Error
2008
	 */
2009
	public function get_first_related($relationName,$query_params = array()){
2010
		if($this->ID()){//this exists in the DB, get from the cache OR the DB
2011
2012
			//if they've provided some query parameters, don't bother trying to cache the result
2013
			//also make sure we're not caching the result of get_first_related
2014
			//on a relation which should have an array of objects (because the cache might have an array of objects)
2015
			if ($query_params || ! $this->get_model()->related_settings_for($relationName) instanceof EE_Belongs_To_Relation){
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...
2016
				$related_model_object =  $this->get_model()->get_first_related($this, $relationName, $query_params);
2017 View Code Duplication
			}else{
2018
				//first, check if we've already cached the result of this query
2019
				$cached_result = $this->get_one_from_cache($relationName);
2020
				if ( ! $cached_result ){
2021
2022
					$related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2023
					$this->cache($relationName,$related_model_object);
2024
				}else{
2025
					$related_model_object = $cached_result;
2026
				}
2027
			}
2028
		}else{
2029
			$related_model_object = null;
2030
			//this doesn't exist in the Db, but maybe the relation is of type belongs to, and so the related thing might
2031
			if( $this->get_model()->related_settings_for($relationName) instanceof EE_Belongs_To_Relation){
2032
				$related_model_object =  $this->get_model()->get_first_related($this, $relationName, $query_params);
2033
			}
2034
			//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
2035
			if( ! $related_model_object){
2036
				$related_model_object = $this->get_one_from_cache($relationName);
2037
			}
2038
2039
		}
2040
		return $related_model_object;
2041
	}
2042
2043
2044
2045
	/**
2046
	 * Does a delete on all related objects of type $relationName and removes
2047
	 * the current model object's relation to them. If they can't be deleted (because
2048
	 * of blocking related model objects) does nothing. If the related model objects are
2049
	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2050
	 * If this model object doesn't exist yet in the DB, just removes its related things
2051
	 *
2052
	 * @param string $relationName
2053
	 * @param array  $query_params like EEM_Base::get_all's
2054
	 * @return int how many deleted
2055
	 * @throws \EE_Error
2056
	 */
2057 View Code Duplication
	public function delete_related($relationName,$query_params = array()){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2058
		if($this->ID()){
2059
			$count =  $this->get_model()->delete_related($this, $relationName, $query_params);
2060
		}else{
2061
			$count = count($this->get_all_from_cache($relationName));
2062
			$this->clear_cache($relationName,NULL,TRUE);
2063
		}
2064
		return $count;
2065
	}
2066
2067
2068
2069
	/**
2070
	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2071
	 * the current model object's relation to them. If they can't be deleted (because
2072
	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2073
	 * If the related thing isn't a soft-deletable model object, this function is identical
2074
	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2075
	 *
2076
	 * @param string $relationName
2077
	 * @param array  $query_params like EEM_Base::get_all's
2078
	 * @return int how many deleted (including those soft deleted)
2079
	 * @throws \EE_Error
2080
	 */
2081 View Code Duplication
	public function delete_related_permanently($relationName,$query_params = array()){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2082
		if($this->ID()){
2083
			$count =  $this->get_model()->delete_related_permanently($this, $relationName, $query_params);
2084
		}else{
2085
			$count = count($this->get_all_from_cache($relationName));
2086
		}
2087
		$this->clear_cache($relationName,NULL,TRUE);
2088
		return $count;
2089
	}
2090
2091
2092
2093
2094
2095
	/**
2096
	 * is_set
2097
	 * Just a simple utility function children can use for checking if property exists
2098
	 *
2099
	 * @access  public
2100
	 * @param  string $field_name property to check
2101
	 * @return bool            				  TRUE if existing,FALSE if not.
2102
	 */
2103
	public function is_set( $field_name ) {
2104
		return isset($this->_fields[$field_name]);
2105
	}
2106
2107
2108
2109
	/**
2110
	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an EE_Error exception if they don't
2111
	 * @param  mixed (string|array) $properties properties to check
2112
	 * @throws EE_Error
2113
	 * @return bool                              TRUE if existing, throw EE_Error if not.
2114
	 */
2115
	protected function _property_exists( $properties ) {
2116
2117
		foreach ( (array) $properties as $property_name ) {
2118
			//first make sure this property exists
2119
			if ( ! $this->_fields[ $property_name ] ) {
2120
				throw new EE_Error(
2121
					sprintf(
2122
						__(
2123
							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2124
							'event_espresso'
2125
						),
2126
						$property_name
2127
					)
2128
				);
2129
			}
2130
		}
2131
2132
		return TRUE;
2133
	}
2134
2135
2136
2137
	/**
2138
	 * This simply returns an array of model fields for this object
2139
	 *
2140
	 * @return array
2141
	 * @throws \EE_Error
2142
	 */
2143
	public function model_field_array() {
2144
		$fields = $this->get_model()->field_settings(FALSE);
2145
		$properties = array();
2146
		//remove prepended underscore
2147
		foreach ( $fields as $field_name => $settings ) {
2148
			$properties[$field_name] = $this->get($field_name);
2149
		}
2150
		return $properties;
2151
	}
2152
2153
2154
2155
	/**
2156
	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2157
	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called (http://www.garfieldtech.com/blog/php-magic-call)
2158
	 * and passed the method's name and arguments.
2159
	 * Instead of requiring a plugin to extend the EE_Base_Class (which works fine is there's only 1 plugin, but when will that happen?)
2160
	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg, filters_hook_espresso__EE_Answer__my_great_function)
2161
	 * and accepts 2 arguments: the object on which the function was called, and an array of the original arguments passed to the function. Whatever their callback function returns will be returned by this function.
2162
	 * Example: in functions.php (or in a plugin):
2163
	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2164
	 * function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2165
	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2166
	 *        return $previousReturnValue.$returnString;
2167
	 * }
2168
	 * require('EE_Answer.class.php');
2169
	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2170
	 * echo $answer->my_callback('monkeys',100);
2171
	 * //will output "you called my_callback! and passed args:monkeys,100"
2172
	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2173
	 * @param array  $args       array of original arguments passed to the function
2174
	 * @throws EE_Error
2175
	 * @return mixed whatever the plugin which calls add_filter decides
2176
	 */
2177
	public function __call($methodName,$args){
2178
		$className=get_class($this);
2179
		$tagName="FHEE__{$className}__{$methodName}";
2180
		if ( ! has_filter( $tagName ) ) {
2181
			throw new EE_Error(
2182
				sprintf(
2183
					__(
2184
						"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;}",
2185
						"event_espresso"
2186
					),
2187
					$methodName,
2188
					$className,
2189
					$tagName
2190
				)
2191
			);
2192
		}
2193
		return apply_filters($tagName,null,$this,$args);
2194
	}
2195
2196
2197
2198
	/**
2199
	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2200
	 * A $previous_value can be specified in case there are many meta rows with the same key
2201
	 *
2202
	 * @param string $meta_key
2203
	 * @param string $meta_value
2204
	 * @param string $previous_value
2205
	 * @return int records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2206
	 * @throws \EE_Error
2207
	 * NOTE: if the values haven't changed, returns 0
2208
	 */
2209
	public function update_extra_meta($meta_key,$meta_value,$previous_value = NULL){
2210
		$query_params = array(
2211
			array(
2212
				'EXM_key'  => $meta_key,
2213
				'OBJ_ID'   => $this->ID(),
2214
				'EXM_type' => $this->get_model()->get_this_model_name()
2215
			)
2216
		);
2217
		if ( $previous_value !== null ) {
2218
			$query_params[0]['EXM_value'] = $meta_value;
2219
		}
2220
		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all( $query_params );
2221
		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...
2222
			return $this->add_extra_meta( $meta_key, $meta_value );
2223
		} else {
2224
			foreach ( $existing_rows_like_that as $existing_row ) {
2225
				$existing_row->save( array( 'EXM_value' => $meta_value ) );
2226
			}
2227
			return count( $existing_rows_like_that );
2228
		}
2229
	}
2230
2231
2232
2233
	/**
2234
	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2235
	 * no other extra meta for this model object have the same key. Returns TRUE if the
2236
	 * extra meta row was entered, false if not
2237
	 *
2238
	 * @param string  $meta_key
2239
	 * @param string  $meta_value
2240
	 * @param boolean $unique
2241
	 * @return boolean
2242
	 * @throws \EE_Error
2243
	 */
2244
	public function add_extra_meta($meta_key,$meta_value,$unique = false){
2245
		if ( $unique ) {
2246
			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2247
				array(
2248
					array(
2249
						'EXM_key'  => $meta_key,
2250
						'OBJ_ID'   => $this->ID(),
2251
						'EXM_type' => $this->get_model()->get_this_model_name()
2252
					)
2253
				)
2254
			);
2255
			if ( $existing_extra_meta ) {
2256
				return false;
2257
			}
2258
		}
2259
		$new_extra_meta = EE_Extra_Meta::new_instance(
2260
			array(
2261
				'EXM_key'   => $meta_key,
2262
				'EXM_value' => $meta_value,
2263
				'OBJ_ID'    => $this->ID(),
2264
				'EXM_type'  => $this->get_model()->get_this_model_name()
2265
			)
2266
		);
2267
		$new_extra_meta->save();
2268
		return true;
2269
	}
2270
2271
2272
2273
	/**
2274
	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2275
	 * is specified, only deletes extra meta records with that value.
2276
	 *
2277
	 * @param string $meta_key
2278
	 * @param string $meta_value
2279
	 * @return int number of extra meta rows deleted
2280
	 * @throws \EE_Error
2281
	 */
2282
	public function delete_extra_meta($meta_key,$meta_value = NULL){
2283
		$query_params = array(
2284
			array(
2285
				'EXM_key'  => $meta_key,
2286
				'OBJ_ID'   => $this->ID(),
2287
				'EXM_type' => $this->get_model()->get_this_model_name()
2288
			)
2289
		);
2290
		if ( $meta_value !== null ) {
2291
			$query_params[0]['EXM_value'] = $meta_value;
2292
		}
2293
		return EEM_Extra_Meta::instance()->delete( $query_params );
2294
	}
2295
2296
2297
2298
	/**
2299
	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2300
	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2301
	 * You can specify $default is case you haven't found the extra meta
2302
	 *
2303
	 * @param string  $meta_key
2304
	 * @param boolean $single
2305
	 * @param mixed   $default if we don't find anything, what should we return?
2306
	 * @return mixed single value if $single; array if ! $single
2307
	 * @throws \EE_Error
2308
	 */
2309
	public function get_extra_meta($meta_key,$single = FALSE,$default = NULL){
2310
		if($single){
2311
			$result = $this->get_first_related('Extra_Meta',array(array('EXM_key'=>$meta_key)));
2312
			if ( $result instanceof EE_Extra_Meta ){
2313
				return $result->value();
2314
			}else{
2315
				return $default;
2316
			}
2317
		}else{
2318
			$results =  $this->get_many_related('Extra_Meta',array(array('EXM_key'=>$meta_key)));
2319
			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...
2320
				$values = array();
2321
				foreach($results as $result){
2322
					if ( $result instanceof EE_Extra_Meta ){
2323
						$values[$result->ID()] = $result->value();
2324
					}
2325
				}
2326
				return $values;
2327
			}else{
2328
				return $default;
2329
			}
2330
		}
2331
2332
	}
2333
2334
2335
2336
	/**
2337
	 * Returns a simple array of all the extra meta associated with this model object.
2338
	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2339
	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2340
	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2341
	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2342
	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2343
	 * finally the extra meta's value as each sub-value. (eg array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2344
	 *
2345
	 * @param boolean $one_of_each_key
2346
	 * @return array
2347
	 * @throws \EE_Error
2348
	 */
2349
	public function all_extra_meta_array($one_of_each_key = true){
2350
		$return_array = array();
2351
		if($one_of_each_key){
2352
			$extra_meta_objs = $this->get_many_related('Extra_Meta', array('group_by'=>'EXM_key'));
2353
			foreach($extra_meta_objs as $extra_meta_obj){
2354
				if ( $extra_meta_obj instanceof EE_Extra_Meta ) {
2355
					$return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2356
				}
2357
			}
2358
		}else{
2359
			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2360
			foreach($extra_meta_objs as $extra_meta_obj){
2361
				if ( $extra_meta_obj instanceof EE_Extra_Meta ) {
2362
					if( ! isset($return_array[$extra_meta_obj->key()])){
2363
						$return_array[$extra_meta_obj->key()] = array();
2364
					}
2365
					$return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2366
				}
2367
			}
2368
		}
2369
		return $return_array;
2370
	}
2371
2372
2373
2374
	/**
2375
	 * Gets a pretty nice displayable nice for this model object. Often overridden
2376
	 *
2377
	 * @return string
2378
	 * @throws \EE_Error
2379
	 */
2380
	public function name(){
2381
		//find a field that's not a text field
2382
		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2383
		if($field_we_can_use){
2384
			return $this->get($field_we_can_use->get_name());
2385
		}else{
2386
			$first_few_properties = $this->model_field_array();
2387
			$first_few_properties = array_slice($first_few_properties,0,3);
2388
			$name_parts = array();
2389
			foreach( $first_few_properties as $name=> $value ){
2390
				$name_parts[] = "$name:$value";
2391
			}
2392
			return implode(",",$name_parts);
2393
		}
2394
	}
2395
2396
2397
2398
	/**
2399
	 * in_entity_map
2400
	 * Checks if this model object has been proven to already be in the entity map
2401
	 *
2402
	 * @return boolean
2403
	 * @throws \EE_Error
2404
	 */
2405
	public function in_entity_map(){
2406
		if( $this->ID() && $this->get_model()->get_from_entity_map( $this->ID() ) === $this ) {
2407
			//well, if we looked, did we find it in the entity map?
2408
			return TRUE;
2409
		}else{
2410
			return FALSE;
2411
		}
2412
	}
2413
2414
	/**
2415
	 * refresh_from_db
2416
	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
2417
	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2418
	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2419
	 */
2420
	public function refresh_from_db(){
2421
		if( $this->ID() && $this->in_entity_map() ){
2422
			$this->get_model()->refresh_entity_map_from_db( $this->ID() );
2423 View Code Duplication
		}else{
2424
			//if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2425
			//if it has an ID but it's not in the map, and you're asking me to refresh it
2426
			//that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2427
			//absolutely nothing in it for this ID
2428
			if( WP_DEBUG ) {
2429
				throw new EE_Error(
2430
					sprintf(
2431
						__( '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.', 'event_espresso' ),
2432
						$this->ID(),
2433
						get_class( $this->get_model() ) . '::instance()->add_to_entity_map()',
2434
						get_class( $this->get_model() ) . '::instance()->refresh_entity_map()'
2435
					)
2436
				);
2437
			}
2438
		}
2439
	}
2440
2441
2442
2443
	/**
2444
	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
2445
	 * (probably a bad assumption they have made, oh well)
2446
	 * @return string
2447
	 */
2448
	public function __toString(){
2449
		try {
2450
			return sprintf( '%s (%s)', $this->name(), $this->ID() );
2451
		} catch ( Exception $e ) {
2452
			EE_Error::add_error( $e->getMessage(), __FILE__, __FUNCTION__, __LINE__ );
2453
			return '';
2454
		}
2455
	}
2456
2457
2458
2459
	/**
2460
	 * Clear related model objects if they're already in the DB, because otherwise when we
2461
	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
2462
	 * This means if we have made changes to those related model objects, and want to unserialize
2463
	 * the this model object on a subsequent request, changes to those related model objects will be lost.
2464
	 * Instead, those related model objects should be directly serialized and stored.
2465
	 * Eg, the following won't work:
2466
	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2467
	 * $att = $reg->attendee();
2468
	 * $att->set( 'ATT_fname', 'Dirk' );
2469
	 * update_option( 'my_option', serialize( $reg ) );
2470
	 * //END REQUEST
2471
	 * //START NEXT REQUEST
2472
	 * $reg = get_option( 'my_option' );
2473
	 * $reg->attendee()->save();
2474
	 * And would need to be replace with:
2475
	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2476
	 * $att = $reg->attendee();
2477
	 * $att->set( 'ATT_fname', 'Dirk' );
2478
	 * update_option( 'my_option', serialize( $reg ) );
2479
	 * //END REQUEST
2480
	 * //START NEXT REQUEST
2481
	 * $att = get_option( 'my_option' );
2482
	 * $att->save();
2483
	 *
2484
	 * @return array
2485
	 * @throws \EE_Error
2486
	 */
2487
	public function __sleep() {
2488
		foreach( $this->get_model()->relation_settings() as $relation_name => $relation_obj ) {
2489
			if( $relation_obj instanceof EE_Belongs_To_Relation ) {
2490
				$classname = 'EE_' . $this->get_model()->get_this_model_name();
2491
				if(
2492
					$this->get_one_from_cache( $relation_name ) instanceof $classname
2493
					&& $this->get_one_from_cache( $relation_name )->ID()
2494
				) {
2495
					$this->clear_cache( $relation_name, $this->get_one_from_cache( $relation_name )->ID() );
2496
				}
2497
			}
2498
		}
2499
		$this->_props_n_values_provided_in_constructor = array();
2500
		return array_keys( get_object_vars( $this ) );
2501
	}
2502
2503
2504
2505
	/**
2506
	 * restore _props_n_values_provided_in_constructor
2507
	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
2508
	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
2509
	 * At best, you would only be able to detect if state change has occurred during THIS request.
2510
	 */
2511
	public function __wakeup() {
2512
		$this->_props_n_values_provided_in_constructor = $this->_fields;
2513
	}
2514
2515
2516
2517
}
2518
2519
2520