Completed
Branch BUG/11294/expired-cart-ticket-... (b6cebb)
by
unknown
17:17 queued 52s
created
core/EE_Session.core.php 2 patches
Indentation   +1179 added lines, -1179 removed lines patch added patch discarded remove patch
@@ -22,1180 +22,1180 @@  discard block
 block discarded – undo
22 22
 class EE_Session implements SessionIdentifierInterface
23 23
 {
24 24
 
25
-    const session_id_prefix    = 'ee_ssn_';
26
-
27
-    const hash_check_prefix    = 'ee_shc_';
28
-
29
-    const OPTION_NAME_SETTINGS = 'ee_session_settings';
30
-
31
-    /**
32
-     * instance of the EE_Session object
33
-     *
34
-     * @var EE_Session
35
-     */
36
-    private static $_instance;
37
-
38
-    /**
39
-     * @var CacheStorageInterface $cache_storage
40
-     */
41
-    protected $cache_storage;
42
-
43
-    /**
44
-     * EE_Encryption object
45
-     *
46
-     * @var EE_Encryption
47
-     */
48
-    protected $encryption;
49
-
50
-    /**
51
-     * the session id
52
-     *
53
-     * @var string
54
-     */
55
-    private $_sid;
56
-
57
-    /**
58
-     * session id salt
59
-     *
60
-     * @var string
61
-     */
62
-    private $_sid_salt;
63
-
64
-    /**
65
-     * session data
66
-     *
67
-     * @var array
68
-     */
69
-    private $_session_data = array();
70
-
71
-    /**
72
-     * how long an EE session lasts
73
-     * default session lifespan of 1 hour (for not so instant IPNs)
74
-     *
75
-     * @var SessionLifespan $session_lifespan
76
-     */
77
-    private $session_lifespan;
78
-
79
-    /**
80
-     * session expiration time as Unix timestamp in GMT
81
-     *
82
-     * @var int
83
-     */
84
-    private $_expiration;
85
-
86
-    /**
87
-     * whether or not session has expired at some point
88
-     *
89
-     * @var boolean
90
-     */
91
-    private $_expired = false;
92
-
93
-    /**
94
-     * current time as Unix timestamp in GMT
95
-     *
96
-     * @var int
97
-     */
98
-    private $_time;
99
-
100
-    /**
101
-     * whether to encrypt session data
102
-     *
103
-     * @var bool
104
-     */
105
-    private $_use_encryption;
106
-
107
-    /**
108
-     * well... according to the server...
109
-     *
110
-     * @var null
111
-     */
112
-    private $_user_agent;
113
-
114
-    /**
115
-     * do you really trust the server ?
116
-     *
117
-     * @var null
118
-     */
119
-    private $_ip_address;
120
-
121
-    /**
122
-     * current WP user_id
123
-     *
124
-     * @var null
125
-     */
126
-    private $_wp_user_id;
127
-
128
-    /**
129
-     * array for defining default session vars
130
-     *
131
-     * @var array
132
-     */
133
-    private $_default_session_vars = array(
134
-        'id'            => null,
135
-        'user_id'       => null,
136
-        'ip_address'    => null,
137
-        'user_agent'    => null,
138
-        'init_access'   => null,
139
-        'last_access'   => null,
140
-        'expiration'    => null,
141
-        'pages_visited' => array(),
142
-    );
143
-
144
-    /**
145
-     * timestamp for when last garbage collection cycle was performed
146
-     *
147
-     * @var int $_last_gc
148
-     */
149
-    private $_last_gc;
150
-
151
-    /**
152
-     * @var RequestInterface $request
153
-     */
154
-    protected $request;
155
-
156
-
157
-    /**
158
-     * @singleton method used to instantiate class object
159
-     * @param CacheStorageInterface $cache_storage
160
-     * @param SessionLifespan|null  $lifespan
161
-     * @param RequestInterface      $request
162
-     * @param EE_Encryption         $encryption
163
-     * @return EE_Session
164
-     * @throws InvalidArgumentException
165
-     * @throws InvalidDataTypeException
166
-     * @throws InvalidInterfaceException
167
-     */
168
-    public static function instance(
169
-        CacheStorageInterface $cache_storage = null,
170
-        SessionLifespan $lifespan = null,
171
-        RequestInterface $request = null,
172
-        EE_Encryption $encryption = null
173
-    ) {
174
-        // check if class object is instantiated
175
-        // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via:
176
-        // add_filter( 'FHEE_load_EE_Session', '__return_false' );
177
-        if (! self::$_instance instanceof EE_Session && apply_filters('FHEE_load_EE_Session', true)) {
178
-            self::$_instance = new self(
179
-                $cache_storage,
180
-                $lifespan,
181
-                $request,
182
-                $encryption
183
-            );
184
-        }
185
-        return self::$_instance;
186
-    }
187
-
188
-
189
-    /**
190
-     * protected constructor to prevent direct creation
191
-     *
192
-     * @param CacheStorageInterface $cache_storage
193
-     * @param SessionLifespan       $lifespan
194
-     * @param RequestInterface      $request
195
-     * @param EE_Encryption         $encryption
196
-     * @throws InvalidArgumentException
197
-     * @throws InvalidDataTypeException
198
-     * @throws InvalidInterfaceException
199
-     */
200
-    protected function __construct(
201
-        CacheStorageInterface $cache_storage,
202
-        SessionLifespan $lifespan,
203
-        RequestInterface $request,
204
-        EE_Encryption $encryption = null
205
-    ) {
206
-        // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via: add_filter( 'FHEE_load_EE_Session', '__return_false' );
207
-        if (! apply_filters('FHEE_load_EE_Session', true)) {
208
-            return;
209
-        }
210
-        $this->session_lifespan = $lifespan;
211
-        $this->request          = $request;
212
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
213
-        if (! defined('ESPRESSO_SESSION')) {
214
-            define('ESPRESSO_SESSION', true);
215
-        }
216
-        // retrieve session options from db
217
-        $session_settings = (array) get_option(EE_Session::OPTION_NAME_SETTINGS, array());
218
-        if (! empty($session_settings)) {
219
-            // cycle though existing session options
220
-            foreach ($session_settings as $var_name => $session_setting) {
221
-                // set values for class properties
222
-                $var_name          = '_' . $var_name;
223
-                $this->{$var_name} = $session_setting;
224
-            }
225
-        }
226
-        $this->cache_storage = $cache_storage;
227
-        // are we using encryption?
228
-        $this->_use_encryption = $encryption instanceof EE_Encryption
229
-                                 && EE_Registry::instance()->CFG->admin->encode_session_data();
230
-        // \EEH_Debug_Tools::printr($this->_use_encryption, '$this->_use_encryption', __FILE__, __LINE__);
231
-        // encrypt data via: $this->encryption->encrypt();
232
-        $this->encryption = $encryption;
233
-        // filter hook allows outside functions/classes/plugins to change default empty cart
234
-        $extra_default_session_vars = apply_filters('FHEE__EE_Session__construct__extra_default_session_vars', array());
235
-        array_merge($this->_default_session_vars, $extra_default_session_vars);
236
-        // apply default session vars
237
-        $this->_set_defaults();
238
-        add_action('AHEE__EE_System__initialize', array($this, 'open_session'));
239
-        // check request for 'clear_session' param
240
-        add_action('AHEE__EE_Request_Handler__construct__complete', array($this, 'wp_loaded'));
241
-        // once everything is all said and done,
242
-        add_action('shutdown', array($this, 'update'), 100);
243
-        add_action('shutdown', array($this, 'garbageCollection'), 1000);
244
-        $this->configure_garbage_collection_filters();
245
-    }
246
-
247
-
248
-
249
-    /**
250
-     * @return void
251
-     * @throws EE_Error
252
-     * @throws InvalidArgumentException
253
-     * @throws InvalidDataTypeException
254
-     * @throws InvalidInterfaceException
255
-     * @throws InvalidSessionDataException
256
-     */
257
-    public function open_session()
258
-    {
259
-        // check for existing session and retrieve it from db
260
-        if (! $this->_espresso_session()) {
261
-            // or just start a new one
262
-            $this->_create_espresso_session();
263
-        }
264
-    }
265
-
266
-
267
-
268
-    /**
269
-     * @return bool
270
-     */
271
-    public function expired()
272
-    {
273
-        return $this->_expired;
274
-    }
275
-
276
-
277
-
278
-    /**
279
-     * @return void
280
-     */
281
-    public function reset_expired()
282
-    {
283
-        $this->_expired = false;
284
-    }
285
-
286
-
287
-    /**
288
-     * @return int
289
-     */
290
-    public function expiration()
291
-    {
292
-        return $this->_expiration;
293
-    }
294
-
295
-
296
-
297
-    /**
298
-     * @return int
299
-     */
300
-    public function extension()
301
-    {
302
-        return apply_filters('FHEE__EE_Session__extend_expiration__seconds_added', 10 * MINUTE_IN_SECONDS);
303
-    }
304
-
305
-
306
-
307
-    /**
308
-     * @param int $time number of seconds to add to session expiration
309
-     */
310
-    public function extend_expiration($time = 0)
311
-    {
312
-        $time              = $time ? $time : $this->extension();
313
-        $this->_expiration += absint($time);
314
-    }
315
-
316
-
317
-
318
-    /**
319
-     * @return int
320
-     */
321
-    public function lifespan()
322
-    {
323
-        return $this->session_lifespan->inSeconds();
324
-    }
325
-
326
-
327
-
328
-    /**
329
-     * This just sets some defaults for the _session data property
330
-     *
331
-     * @access private
332
-     * @return void
333
-     */
334
-    private function _set_defaults()
335
-    {
336
-        // set some defaults
337
-        foreach ($this->_default_session_vars as $key => $default_var) {
338
-            if (is_array($default_var)) {
339
-                $this->_session_data[ $key ] = array();
340
-            } else {
341
-                $this->_session_data[ $key ] = '';
342
-            }
343
-        }
344
-    }
25
+	const session_id_prefix    = 'ee_ssn_';
26
+
27
+	const hash_check_prefix    = 'ee_shc_';
28
+
29
+	const OPTION_NAME_SETTINGS = 'ee_session_settings';
30
+
31
+	/**
32
+	 * instance of the EE_Session object
33
+	 *
34
+	 * @var EE_Session
35
+	 */
36
+	private static $_instance;
37
+
38
+	/**
39
+	 * @var CacheStorageInterface $cache_storage
40
+	 */
41
+	protected $cache_storage;
42
+
43
+	/**
44
+	 * EE_Encryption object
45
+	 *
46
+	 * @var EE_Encryption
47
+	 */
48
+	protected $encryption;
49
+
50
+	/**
51
+	 * the session id
52
+	 *
53
+	 * @var string
54
+	 */
55
+	private $_sid;
56
+
57
+	/**
58
+	 * session id salt
59
+	 *
60
+	 * @var string
61
+	 */
62
+	private $_sid_salt;
63
+
64
+	/**
65
+	 * session data
66
+	 *
67
+	 * @var array
68
+	 */
69
+	private $_session_data = array();
70
+
71
+	/**
72
+	 * how long an EE session lasts
73
+	 * default session lifespan of 1 hour (for not so instant IPNs)
74
+	 *
75
+	 * @var SessionLifespan $session_lifespan
76
+	 */
77
+	private $session_lifespan;
78
+
79
+	/**
80
+	 * session expiration time as Unix timestamp in GMT
81
+	 *
82
+	 * @var int
83
+	 */
84
+	private $_expiration;
85
+
86
+	/**
87
+	 * whether or not session has expired at some point
88
+	 *
89
+	 * @var boolean
90
+	 */
91
+	private $_expired = false;
92
+
93
+	/**
94
+	 * current time as Unix timestamp in GMT
95
+	 *
96
+	 * @var int
97
+	 */
98
+	private $_time;
99
+
100
+	/**
101
+	 * whether to encrypt session data
102
+	 *
103
+	 * @var bool
104
+	 */
105
+	private $_use_encryption;
106
+
107
+	/**
108
+	 * well... according to the server...
109
+	 *
110
+	 * @var null
111
+	 */
112
+	private $_user_agent;
113
+
114
+	/**
115
+	 * do you really trust the server ?
116
+	 *
117
+	 * @var null
118
+	 */
119
+	private $_ip_address;
120
+
121
+	/**
122
+	 * current WP user_id
123
+	 *
124
+	 * @var null
125
+	 */
126
+	private $_wp_user_id;
127
+
128
+	/**
129
+	 * array for defining default session vars
130
+	 *
131
+	 * @var array
132
+	 */
133
+	private $_default_session_vars = array(
134
+		'id'            => null,
135
+		'user_id'       => null,
136
+		'ip_address'    => null,
137
+		'user_agent'    => null,
138
+		'init_access'   => null,
139
+		'last_access'   => null,
140
+		'expiration'    => null,
141
+		'pages_visited' => array(),
142
+	);
143
+
144
+	/**
145
+	 * timestamp for when last garbage collection cycle was performed
146
+	 *
147
+	 * @var int $_last_gc
148
+	 */
149
+	private $_last_gc;
150
+
151
+	/**
152
+	 * @var RequestInterface $request
153
+	 */
154
+	protected $request;
155
+
156
+
157
+	/**
158
+	 * @singleton method used to instantiate class object
159
+	 * @param CacheStorageInterface $cache_storage
160
+	 * @param SessionLifespan|null  $lifespan
161
+	 * @param RequestInterface      $request
162
+	 * @param EE_Encryption         $encryption
163
+	 * @return EE_Session
164
+	 * @throws InvalidArgumentException
165
+	 * @throws InvalidDataTypeException
166
+	 * @throws InvalidInterfaceException
167
+	 */
168
+	public static function instance(
169
+		CacheStorageInterface $cache_storage = null,
170
+		SessionLifespan $lifespan = null,
171
+		RequestInterface $request = null,
172
+		EE_Encryption $encryption = null
173
+	) {
174
+		// check if class object is instantiated
175
+		// session loading is turned ON by default, but prior to the init hook, can be turned back OFF via:
176
+		// add_filter( 'FHEE_load_EE_Session', '__return_false' );
177
+		if (! self::$_instance instanceof EE_Session && apply_filters('FHEE_load_EE_Session', true)) {
178
+			self::$_instance = new self(
179
+				$cache_storage,
180
+				$lifespan,
181
+				$request,
182
+				$encryption
183
+			);
184
+		}
185
+		return self::$_instance;
186
+	}
187
+
188
+
189
+	/**
190
+	 * protected constructor to prevent direct creation
191
+	 *
192
+	 * @param CacheStorageInterface $cache_storage
193
+	 * @param SessionLifespan       $lifespan
194
+	 * @param RequestInterface      $request
195
+	 * @param EE_Encryption         $encryption
196
+	 * @throws InvalidArgumentException
197
+	 * @throws InvalidDataTypeException
198
+	 * @throws InvalidInterfaceException
199
+	 */
200
+	protected function __construct(
201
+		CacheStorageInterface $cache_storage,
202
+		SessionLifespan $lifespan,
203
+		RequestInterface $request,
204
+		EE_Encryption $encryption = null
205
+	) {
206
+		// session loading is turned ON by default, but prior to the init hook, can be turned back OFF via: add_filter( 'FHEE_load_EE_Session', '__return_false' );
207
+		if (! apply_filters('FHEE_load_EE_Session', true)) {
208
+			return;
209
+		}
210
+		$this->session_lifespan = $lifespan;
211
+		$this->request          = $request;
212
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
213
+		if (! defined('ESPRESSO_SESSION')) {
214
+			define('ESPRESSO_SESSION', true);
215
+		}
216
+		// retrieve session options from db
217
+		$session_settings = (array) get_option(EE_Session::OPTION_NAME_SETTINGS, array());
218
+		if (! empty($session_settings)) {
219
+			// cycle though existing session options
220
+			foreach ($session_settings as $var_name => $session_setting) {
221
+				// set values for class properties
222
+				$var_name          = '_' . $var_name;
223
+				$this->{$var_name} = $session_setting;
224
+			}
225
+		}
226
+		$this->cache_storage = $cache_storage;
227
+		// are we using encryption?
228
+		$this->_use_encryption = $encryption instanceof EE_Encryption
229
+								 && EE_Registry::instance()->CFG->admin->encode_session_data();
230
+		// \EEH_Debug_Tools::printr($this->_use_encryption, '$this->_use_encryption', __FILE__, __LINE__);
231
+		// encrypt data via: $this->encryption->encrypt();
232
+		$this->encryption = $encryption;
233
+		// filter hook allows outside functions/classes/plugins to change default empty cart
234
+		$extra_default_session_vars = apply_filters('FHEE__EE_Session__construct__extra_default_session_vars', array());
235
+		array_merge($this->_default_session_vars, $extra_default_session_vars);
236
+		// apply default session vars
237
+		$this->_set_defaults();
238
+		add_action('AHEE__EE_System__initialize', array($this, 'open_session'));
239
+		// check request for 'clear_session' param
240
+		add_action('AHEE__EE_Request_Handler__construct__complete', array($this, 'wp_loaded'));
241
+		// once everything is all said and done,
242
+		add_action('shutdown', array($this, 'update'), 100);
243
+		add_action('shutdown', array($this, 'garbageCollection'), 1000);
244
+		$this->configure_garbage_collection_filters();
245
+	}
246
+
247
+
248
+
249
+	/**
250
+	 * @return void
251
+	 * @throws EE_Error
252
+	 * @throws InvalidArgumentException
253
+	 * @throws InvalidDataTypeException
254
+	 * @throws InvalidInterfaceException
255
+	 * @throws InvalidSessionDataException
256
+	 */
257
+	public function open_session()
258
+	{
259
+		// check for existing session and retrieve it from db
260
+		if (! $this->_espresso_session()) {
261
+			// or just start a new one
262
+			$this->_create_espresso_session();
263
+		}
264
+	}
265
+
266
+
267
+
268
+	/**
269
+	 * @return bool
270
+	 */
271
+	public function expired()
272
+	{
273
+		return $this->_expired;
274
+	}
275
+
276
+
277
+
278
+	/**
279
+	 * @return void
280
+	 */
281
+	public function reset_expired()
282
+	{
283
+		$this->_expired = false;
284
+	}
285
+
286
+
287
+	/**
288
+	 * @return int
289
+	 */
290
+	public function expiration()
291
+	{
292
+		return $this->_expiration;
293
+	}
294
+
295
+
296
+
297
+	/**
298
+	 * @return int
299
+	 */
300
+	public function extension()
301
+	{
302
+		return apply_filters('FHEE__EE_Session__extend_expiration__seconds_added', 10 * MINUTE_IN_SECONDS);
303
+	}
304
+
305
+
306
+
307
+	/**
308
+	 * @param int $time number of seconds to add to session expiration
309
+	 */
310
+	public function extend_expiration($time = 0)
311
+	{
312
+		$time              = $time ? $time : $this->extension();
313
+		$this->_expiration += absint($time);
314
+	}
315
+
316
+
317
+
318
+	/**
319
+	 * @return int
320
+	 */
321
+	public function lifespan()
322
+	{
323
+		return $this->session_lifespan->inSeconds();
324
+	}
325
+
326
+
327
+
328
+	/**
329
+	 * This just sets some defaults for the _session data property
330
+	 *
331
+	 * @access private
332
+	 * @return void
333
+	 */
334
+	private function _set_defaults()
335
+	{
336
+		// set some defaults
337
+		foreach ($this->_default_session_vars as $key => $default_var) {
338
+			if (is_array($default_var)) {
339
+				$this->_session_data[ $key ] = array();
340
+			} else {
341
+				$this->_session_data[ $key ] = '';
342
+			}
343
+		}
344
+	}
345 345
 
346 346
 
347
-
348
-    /**
349
-     * @retrieve  session data
350
-     * @access    public
351
-     * @return    string
352
-     */
353
-    public function id()
354
-    {
355
-        return $this->_sid;
356
-    }
347
+
348
+	/**
349
+	 * @retrieve  session data
350
+	 * @access    public
351
+	 * @return    string
352
+	 */
353
+	public function id()
354
+	{
355
+		return $this->_sid;
356
+	}
357 357
 
358 358
 
359 359
 
360
-    /**
361
-     * @param \EE_Cart $cart
362
-     * @return bool
363
-     */
364
-    public function set_cart(EE_Cart $cart)
365
-    {
366
-        $this->_session_data['cart'] = $cart;
367
-        return true;
368
-    }
360
+	/**
361
+	 * @param \EE_Cart $cart
362
+	 * @return bool
363
+	 */
364
+	public function set_cart(EE_Cart $cart)
365
+	{
366
+		$this->_session_data['cart'] = $cart;
367
+		return true;
368
+	}
369 369
 
370 370
 
371 371
 
372
-    /**
373
-     * reset_cart
374
-     */
375
-    public function reset_cart()
376
-    {
377
-        do_action('AHEE__EE_Session__reset_cart__before_reset', $this);
378
-        $this->_session_data['cart'] = null;
379
-    }
380
-
381
-
382
-
383
-    /**
384
-     * @return \EE_Cart
385
-     */
386
-    public function cart()
387
-    {
388
-        return isset($this->_session_data['cart']) && $this->_session_data['cart'] instanceof EE_Cart
389
-            ? $this->_session_data['cart']
390
-            : null;
391
-    }
392
-
393
-
394
-
395
-    /**
396
-     * @param \EE_Checkout $checkout
397
-     * @return bool
398
-     */
399
-    public function set_checkout(EE_Checkout $checkout)
400
-    {
401
-        $this->_session_data['checkout'] = $checkout;
402
-        return true;
403
-    }
404
-
405
-
406
-
407
-    /**
408
-     * reset_checkout
409
-     */
410
-    public function reset_checkout()
411
-    {
412
-        do_action('AHEE__EE_Session__reset_checkout__before_reset', $this);
413
-        $this->_session_data['checkout'] = null;
414
-    }
415
-
416
-
417
-
418
-    /**
419
-     * @return \EE_Checkout
420
-     */
421
-    public function checkout()
422
-    {
423
-        return isset($this->_session_data['checkout']) && $this->_session_data['checkout'] instanceof EE_Checkout
424
-            ? $this->_session_data['checkout']
425
-            : null;
426
-    }
427
-
428
-
429
-
430
-    /**
431
-     * @param \EE_Transaction $transaction
432
-     * @return bool
433
-     * @throws EE_Error
434
-     */
435
-    public function set_transaction(EE_Transaction $transaction)
436
-    {
437
-        // first remove the session from the transaction before we save the transaction in the session
438
-        $transaction->set_txn_session_data(null);
439
-        $this->_session_data['transaction'] = $transaction;
440
-        return true;
441
-    }
442
-
443
-
444
-
445
-    /**
446
-     * reset_transaction
447
-     */
448
-    public function reset_transaction()
449
-    {
450
-        do_action('AHEE__EE_Session__reset_transaction__before_reset', $this);
451
-        $this->_session_data['transaction'] = null;
452
-    }
453
-
454
-
455
-
456
-    /**
457
-     * @return \EE_Transaction
458
-     */
459
-    public function transaction()
460
-    {
461
-        return isset($this->_session_data['transaction'])
462
-               && $this->_session_data['transaction'] instanceof EE_Transaction
463
-            ? $this->_session_data['transaction']
464
-            : null;
465
-    }
466
-
467
-
468
-
469
-    /**
470
-     * retrieve session data
471
-     *
472
-     * @access    public
473
-     * @param null $key
474
-     * @param bool $reset_cache
475
-     * @return    array
476
-     */
477
-    public function get_session_data($key = null, $reset_cache = false)
478
-    {
479
-        if ($reset_cache) {
480
-            $this->reset_cart();
481
-            $this->reset_checkout();
482
-            $this->reset_transaction();
483
-        }
484
-        if (! empty($key)) {
485
-            return isset($this->_session_data[ $key ]) ? $this->_session_data[ $key ] : null;
486
-        }
487
-        return $this->_session_data;
488
-    }
489
-
490
-
491
-
492
-    /**
493
-     * set session data
494
-     *
495
-     * @access    public
496
-     * @param    array $data
497
-     * @return    TRUE on success, FALSE on fail
498
-     */
499
-    public function set_session_data($data)
500
-    {
501
-
502
-        // nothing ??? bad data ??? go home!
503
-        if (empty($data) || ! is_array($data)) {
504
-            EE_Error::add_error(__('No session data or invalid session data was provided.', 'event_espresso'), __FILE__,
505
-                __FUNCTION__, __LINE__);
506
-            return false;
507
-        }
508
-        foreach ($data as $key => $value) {
509
-            if (isset($this->_default_session_vars[ $key ])) {
510
-                EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
511
-                    'event_espresso'), $key), __FILE__, __FUNCTION__, __LINE__);
512
-                return false;
513
-            }
514
-            $this->_session_data[ $key ] = $value;
515
-        }
516
-        return true;
517
-    }
518
-
519
-
520
-
521
-    /**
522
-     * @initiate session
523
-     * @access   private
524
-     * @return TRUE on success, FALSE on fail
525
-     * @throws EE_Error
526
-     * @throws InvalidArgumentException
527
-     * @throws InvalidDataTypeException
528
-     * @throws InvalidInterfaceException
529
-     * @throws InvalidSessionDataException
530
-     */
531
-    private function _espresso_session()
532
-    {
533
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
534
-        // check that session has started
535
-        if (session_id() === '') {
536
-            //starts a new session if one doesn't already exist, or re-initiates an existing one
537
-            session_start();
538
-        }
539
-        // get our modified session ID
540
-        $this->_sid = $this->_generate_session_id();
541
-        // and the visitors IP
542
-        $this->_ip_address = $this->request->ipAddress();
543
-        // set the "user agent"
544
-        $this->_user_agent = $this->request->userAgent();
545
-        // now let's retrieve what's in the db
546
-        $session_data = $this->_retrieve_session_data();
547
-        if (! empty($session_data)) {
548
-            // get the current time in UTC
549
-            $this->_time = $this->_time !== null ? $this->_time : time();
550
-            // and reset the session expiration
551
-            $this->_expiration = isset($session_data['expiration'])
552
-                ? $session_data['expiration']
553
-                : $this->_time + $this->session_lifespan->inSeconds();
554
-        } else {
555
-            // set initial site access time and the session expiration
556
-            $this->_set_init_access_and_expiration();
557
-            // set referer
558
-            $this->_session_data['pages_visited'][ $this->_session_data['init_access'] ] = isset($_SERVER['HTTP_REFERER'])
559
-                ? esc_attr($_SERVER['HTTP_REFERER'])
560
-                : '';
561
-            // no previous session = go back and create one (on top of the data above)
562
-            return false;
563
-        }
564
-        // now the user agent
565
-        if ($session_data['user_agent'] !== $this->_user_agent) {
566
-            return false;
567
-        }
568
-        // wait a minute... how old are you?
569
-        if ($this->_time > $this->_expiration) {
570
-            // yer too old fer me!
571
-            $this->_expired = true;
572
-            // wipe out everything that isn't a default session datum
573
-            $this->clear_session(__CLASS__, __FUNCTION__);
574
-        }
575
-        // make event espresso session data available to plugin
576
-        $this->_session_data = array_merge($this->_session_data, $session_data);
577
-        return true;
578
-    }
579
-
580
-
581
-
582
-    /**
583
-     * _get_session_data
584
-     * Retrieves the session data, and attempts to correct any encoding issues that can occur due to improperly setup
585
-     * databases
586
-     *
587
-     * @return array
588
-     * @throws EE_Error
589
-     * @throws InvalidArgumentException
590
-     * @throws InvalidSessionDataException
591
-     * @throws InvalidDataTypeException
592
-     * @throws InvalidInterfaceException
593
-     */
594
-    protected function _retrieve_session_data()
595
-    {
596
-        $ssn_key = EE_Session::session_id_prefix . $this->_sid;
597
-        try {
598
-            // we're using WP's Transient API to store session data using the PHP session ID as the option name
599
-            $session_data = $this->cache_storage->get($ssn_key, false);
600
-            if (empty($session_data)) {
601
-                return array();
602
-            }
603
-            if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
604
-                $hash_check = $this->cache_storage->get(
605
-                    EE_Session::hash_check_prefix . $this->_sid,
606
-                    false
607
-                );
608
-                if ($hash_check && $hash_check !== md5($session_data)) {
609
-                    EE_Error::add_error(
610
-                        sprintf(
611
-                            __(
612
-                                'The stored data for session %1$s failed to pass a hash check and therefore appears to be invalid.',
613
-                                'event_espresso'
614
-                            ),
615
-                            EE_Session::session_id_prefix . $this->_sid
616
-                        ),
617
-                        __FILE__, __FUNCTION__, __LINE__
618
-                    );
619
-                }
620
-            }
621
-        } catch (Exception $e) {
622
-            // let's just eat that error for now and attempt to correct any corrupted data
623
-            global $wpdb;
624
-            $row          = $wpdb->get_row(
625
-                $wpdb->prepare(
626
-                    "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
627
-                    '_transient_' . $ssn_key
628
-                )
629
-            );
630
-            $session_data = is_object($row) ? $row->option_value : null;
631
-            if ($session_data) {
632
-                $session_data = preg_replace_callback(
633
-                    '!s:(d+):"(.*?)";!',
634
-                    function ($match)
635
-                    {
636
-                        return $match[1] === strlen($match[2])
637
-                            ? $match[0]
638
-                            : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
639
-                    },
640
-                    $session_data
641
-                );
642
-            }
643
-            $session_data = maybe_unserialize($session_data);
644
-        }
645
-        // in case the data is encoded... try to decode it
646
-        $session_data = $this->encryption instanceof EE_Encryption
647
-            ? $this->encryption->base64_string_decode($session_data)
648
-            : $session_data;
649
-        if (! is_array($session_data)) {
650
-            try {
651
-                $session_data = maybe_unserialize($session_data);
652
-            } catch (Exception $e) {
653
-                $msg = esc_html__(
654
-                    'An error occurred while attempting to unserialize the session data.',
655
-                    'event_espresso'
656
-                );
657
-                $msg .= WP_DEBUG
658
-                    ? '<br><pre>'
659
-                      . print_r($session_data, true)
660
-                      . '</pre><br>'
661
-                      . $this->find_serialize_error($session_data)
662
-                    : '';
663
-                $this->cache_storage->delete(EE_Session::session_id_prefix . $this->_sid);
664
-                throw new InvalidSessionDataException($msg, 0, $e);
665
-            }
666
-        }
667
-        // just a check to make sure the session array is indeed an array
668
-        if (! is_array($session_data)) {
669
-            // no?!?! then something is wrong
670
-            $msg = esc_html__(
671
-                'The session data is missing, invalid, or corrupted.',
672
-                'event_espresso'
673
-            );
674
-            $msg .= WP_DEBUG
675
-                ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
676
-                : '';
677
-            $this->cache_storage->delete(EE_Session::session_id_prefix . $this->_sid);
678
-            throw new InvalidSessionDataException($msg);
679
-        }
680
-        if (isset($session_data['transaction']) && absint($session_data['transaction']) !== 0) {
681
-            $session_data['transaction'] = EEM_Transaction::instance()->get_one_by_ID(
682
-                $session_data['transaction']
683
-            );
684
-        }
685
-        return $session_data;
686
-    }
687
-
688
-
689
-
690
-    /**
691
-     * _generate_session_id
692
-     * Retrieves the PHP session id either directly from the PHP session,
693
-     * or from the $_REQUEST array if it was passed in from an AJAX request.
694
-     * The session id is then salted and hashed (mmm sounds tasty)
695
-     * so that it can be safely used as a $_REQUEST param
696
-     *
697
-     * @return string
698
-     */
699
-    protected function _generate_session_id()
700
-    {
701
-        // check if the SID was passed explicitly, otherwise get from session, then add salt and hash it to reduce length
702
-        if (isset($_REQUEST['EESID'])) {
703
-            $session_id = sanitize_text_field($_REQUEST['EESID']);
704
-        } else {
705
-            $session_id = md5(session_id() . get_current_blog_id() . $this->_get_sid_salt());
706
-        }
707
-        return apply_filters('FHEE__EE_Session___generate_session_id__session_id', $session_id);
708
-    }
709
-
710
-
711
-
712
-    /**
713
-     * _get_sid_salt
714
-     *
715
-     * @return string
716
-     */
717
-    protected function _get_sid_salt()
718
-    {
719
-        // was session id salt already saved to db ?
720
-        if (empty($this->_sid_salt)) {
721
-            // no?  then maybe use WP defined constant
722
-            if (defined('AUTH_SALT')) {
723
-                $this->_sid_salt = AUTH_SALT;
724
-            }
725
-            // if salt doesn't exist or is too short
726
-            if (strlen($this->_sid_salt) < 32) {
727
-                // create a new one
728
-                $this->_sid_salt = wp_generate_password(64);
729
-            }
730
-            // and save it as a permanent session setting
731
-            $this->updateSessionSettings(array('sid_salt' => $this->_sid_salt));
732
-        }
733
-        return $this->_sid_salt;
734
-    }
735
-
736
-
737
-
738
-    /**
739
-     * _set_init_access_and_expiration
740
-     *
741
-     * @return void
742
-     */
743
-    protected function _set_init_access_and_expiration()
744
-    {
745
-        $this->_time       = time();
746
-        $this->_expiration = $this->_time + $this->session_lifespan->inSeconds();
747
-        // set initial site access time
748
-        $this->_session_data['init_access'] = $this->_time;
749
-        // and the session expiration
750
-        $this->_session_data['expiration'] = $this->_expiration;
751
-    }
752
-
753
-
754
-
755
-    /**
756
-     * @update session data  prior to saving to the db
757
-     * @access public
758
-     * @param bool $new_session
759
-     * @return TRUE on success, FALSE on fail
760
-     * @throws EE_Error
761
-     * @throws InvalidArgumentException
762
-     * @throws InvalidDataTypeException
763
-     * @throws InvalidInterfaceException
764
-     */
765
-    public function update($new_session = false)
766
-    {
767
-        $this->_session_data = $this->_session_data !== null
768
-                               && is_array($this->_session_data)
769
-                               && isset($this->_session_data['id'])
770
-            ? $this->_session_data
771
-            : array();
772
-        if (empty($this->_session_data)) {
773
-            $this->_set_defaults();
774
-        }
775
-        $session_data = array();
776
-        foreach ($this->_session_data as $key => $value) {
777
-
778
-            switch ($key) {
779
-
780
-                case 'id' :
781
-                    // session ID
782
-                    $session_data['id'] = $this->_sid;
783
-                    break;
784
-                case 'ip_address' :
785
-                    // visitor ip address
786
-                    $session_data['ip_address'] = $this->request->ipAddress();
787
-                    break;
788
-                case 'user_agent' :
789
-                    // visitor user_agent
790
-                    $session_data['user_agent'] = $this->_user_agent;
791
-                    break;
792
-                case 'init_access' :
793
-                    $session_data['init_access'] = absint($value);
794
-                    break;
795
-                case 'last_access' :
796
-                    // current access time
797
-                    $session_data['last_access'] = $this->_time;
798
-                    break;
799
-                case 'expiration' :
800
-                    // when the session expires
801
-                    $session_data['expiration'] = ! empty($this->_expiration)
802
-                        ? $this->_expiration
803
-                        : $session_data['init_access'] + $this->session_lifespan->inSeconds();
804
-                    break;
805
-                case 'user_id' :
806
-                    // current user if logged in
807
-                    $session_data['user_id'] = $this->_wp_user_id();
808
-                    break;
809
-                case 'pages_visited' :
810
-                    $page_visit = $this->_get_page_visit();
811
-                    if ($page_visit) {
812
-                        // set pages visited where the first will be the http referrer
813
-                        $this->_session_data['pages_visited'][ $this->_time ] = $page_visit;
814
-                        // we'll only save the last 10 page visits.
815
-                        $session_data['pages_visited'] = array_slice($this->_session_data['pages_visited'], -10);
816
-                    }
817
-                    break;
818
-                default :
819
-                    // carry any other data over
820
-                    $session_data[ $key ] = $this->_session_data[ $key ];
821
-            }
822
-        }
823
-        $this->_session_data = $session_data;
824
-        // creating a new session does not require saving to the db just yet
825
-        if (! $new_session) {
826
-            // ready? let's save
827
-            if ($this->_save_session_to_db()) {
828
-                return true;
829
-            }
830
-            return false;
831
-        }
832
-        // meh, why not?
833
-        return true;
834
-    }
835
-
836
-
837
-
838
-    /**
839
-     * @create session data array
840
-     * @access public
841
-     * @return bool
842
-     * @throws EE_Error
843
-     * @throws InvalidArgumentException
844
-     * @throws InvalidDataTypeException
845
-     * @throws InvalidInterfaceException
846
-     */
847
-    private function _create_espresso_session()
848
-    {
849
-        do_action('AHEE_log', __CLASS__, __FUNCTION__, '');
850
-        // use the update function for now with $new_session arg set to TRUE
851
-        return $this->update(true) ? true : false;
852
-    }
853
-
854
-
855
-
856
-    /**
857
-     * _save_session_to_db
858
-     *
859
-     * @param bool $clear_session
860
-     * @return string
861
-     * @throws EE_Error
862
-     * @throws InvalidArgumentException
863
-     * @throws InvalidDataTypeException
864
-     * @throws InvalidInterfaceException
865
-     */
866
-    private function _save_session_to_db($clear_session = false)
867
-    {
868
-        // don't save sessions for crawlers
869
-        // and unless we're deleting the session data, don't save anything if there isn't a cart
870
-        if ($this->request->isBot() || (! $clear_session && ! $this->cart() instanceof EE_Cart)) {
871
-            return false;
872
-        }
873
-        $transaction = $this->transaction();
874
-        if ($transaction instanceof EE_Transaction) {
875
-            if (! $transaction->ID()) {
876
-                $transaction->save();
877
-            }
878
-            $this->_session_data['transaction'] = $transaction->ID();
879
-        }
880
-        // then serialize all of our session data
881
-        $session_data = serialize($this->_session_data);
882
-        // do we need to also encode it to avoid corrupted data when saved to the db?
883
-        $session_data = $this->_use_encryption
884
-            ? $this->encryption->base64_string_encode($session_data)
885
-            : $session_data;
886
-        // maybe save hash check
887
-        if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
888
-            $this->cache_storage->add(
889
-                EE_Session::hash_check_prefix . $this->_sid,
890
-                md5($session_data),
891
-                $this->session_lifespan->inSeconds()
892
-            );
893
-        }
894
-        // we're using the Transient API for storing session data,
895
-        return $this->cache_storage->add(
896
-            EE_Session::session_id_prefix . $this->_sid,
897
-            $session_data,
898
-            $this->session_lifespan->inSeconds()
899
-        );
900
-    }
901
-
902
-
903
-    /**
904
-     * @get    the full page request the visitor is accessing
905
-     * @access public
906
-     * @return string
907
-     */
908
-    public function _get_page_visit()
909
-    {
910
-        $page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
911
-        // check for request url
912
-        if (isset($_SERVER['REQUEST_URI'])) {
913
-            $http_host   = '';
914
-            $page_id     = '?';
915
-            $e_reg       = '';
916
-            $request_uri = esc_url($_SERVER['REQUEST_URI']);
917
-            $ru_bits     = explode('?', $request_uri);
918
-            $request_uri = $ru_bits[0];
919
-            // check for and grab host as well
920
-            if (isset($_SERVER['HTTP_HOST'])) {
921
-                $http_host = esc_url($_SERVER['HTTP_HOST']);
922
-            }
923
-            // check for page_id in SERVER REQUEST
924
-            if (isset($_REQUEST['page_id'])) {
925
-                // rebuild $e_reg without any of the extra parameters
926
-                $page_id = '?page_id=' . esc_attr($_REQUEST['page_id']) . '&amp;';
927
-            }
928
-            // check for $e_reg in SERVER REQUEST
929
-            if (isset($_REQUEST['ee'])) {
930
-                // rebuild $e_reg without any of the extra parameters
931
-                $e_reg = 'ee=' . esc_attr($_REQUEST['ee']);
932
-            }
933
-            $page_visit = rtrim($http_host . $request_uri . $page_id . $e_reg, '?');
934
-        }
935
-        return $page_visit !== home_url('/wp-admin/admin-ajax.php') ? $page_visit : '';
936
-    }
937
-
938
-
939
-
940
-    /**
941
-     * @the    current wp user id
942
-     * @access public
943
-     * @return int
944
-     */
945
-    public function _wp_user_id()
946
-    {
947
-        // if I need to explain the following lines of code, then you shouldn't be looking at this!
948
-        $this->_wp_user_id = get_current_user_id();
949
-        return $this->_wp_user_id;
950
-    }
951
-
952
-
953
-
954
-    /**
955
-     * Clear EE_Session data
956
-     *
957
-     * @access public
958
-     * @param string $class
959
-     * @param string $function
960
-     * @return void
961
-     * @throws EE_Error
962
-     * @throws InvalidArgumentException
963
-     * @throws InvalidDataTypeException
964
-     * @throws InvalidInterfaceException
965
-     */
966
-    public function clear_session($class = '', $function = '')
967
-    {
968
-        do_action('AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' . $function . '()');
969
-        $this->reset_cart();
970
-        $this->reset_checkout();
971
-        $this->reset_transaction();
972
-        // wipe out everything that isn't a default session datum
973
-        $this->reset_data(array_keys($this->_session_data));
974
-        // reset initial site access time and the session expiration
975
-        $this->_set_init_access_and_expiration();
976
-        $this->_save_session_to_db(true);
977
-    }
978
-
979
-
980
-
981
-    /**
982
-     * @resets all non-default session vars
983
-     * @access public
984
-     * @param array|mixed $data_to_reset
985
-     * @param bool        $show_all_notices
986
-     * @return TRUE on success, FALSE on fail
987
-     */
988
-    public function reset_data($data_to_reset = array(), $show_all_notices = false)
989
-    {
990
-        // if $data_to_reset is not in an array, then put it in one
991
-        if (! is_array($data_to_reset)) {
992
-            $data_to_reset = array($data_to_reset);
993
-        }
994
-        // nothing ??? go home!
995
-        if (empty($data_to_reset)) {
996
-            EE_Error::add_error(__('No session data could be reset, because no session var name was provided.',
997
-                'event_espresso'), __FILE__, __FUNCTION__, __LINE__);
998
-            return false;
999
-        }
1000
-        $return_value = true;
1001
-        // since $data_to_reset is an array, cycle through the values
1002
-        foreach ($data_to_reset as $reset) {
1003
-
1004
-            // first check to make sure it is a valid session var
1005
-            if (isset($this->_session_data[ $reset ])) {
1006
-                // then check to make sure it is not a default var
1007
-                if (! array_key_exists($reset, $this->_default_session_vars)) {
1008
-                    // remove session var
1009
-                    unset($this->_session_data[ $reset ]);
1010
-                    if ($show_all_notices) {
1011
-                        EE_Error::add_success(sprintf(__('The session variable %s was removed.', 'event_espresso'),
1012
-                            $reset), __FILE__, __FUNCTION__, __LINE__);
1013
-                    }
1014
-                } else {
1015
-                    // yeeeeeeeeerrrrrrrrrrr OUT !!!!
1016
-                    if ($show_all_notices) {
1017
-                        EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
1018
-                            'event_espresso'), $reset), __FILE__, __FUNCTION__, __LINE__);
1019
-                    }
1020
-                    $return_value = false;
1021
-                }
1022
-            } elseif ($show_all_notices) {
1023
-                // oops! that session var does not exist!
1024
-                EE_Error::add_error(sprintf(__('The session item provided, %s, is invalid or does not exist.',
1025
-                    'event_espresso'), $reset), __FILE__, __FUNCTION__, __LINE__);
1026
-                $return_value = false;
1027
-            }
1028
-        } // end of foreach
1029
-        return $return_value;
1030
-    }
1031
-
1032
-
1033
-
1034
-    /**
1035
-     *   wp_loaded
1036
-     *
1037
-     * @access public
1038
-     * @throws EE_Error
1039
-     * @throws InvalidDataTypeException
1040
-     * @throws InvalidInterfaceException
1041
-     * @throws InvalidArgumentException
1042
-     */
1043
-    public function wp_loaded()
1044
-    {
1045
-        if ($this->request->requestParamIsSet('clear_session')) {
1046
-            $this->clear_session(__CLASS__, __FUNCTION__);
1047
-        }
1048
-    }
1049
-
1050
-
1051
-
1052
-    /**
1053
-     * Used to reset the entire object (for tests).
1054
-     *
1055
-     * @since 4.3.0
1056
-     * @throws EE_Error
1057
-     * @throws InvalidDataTypeException
1058
-     * @throws InvalidInterfaceException
1059
-     * @throws InvalidArgumentException
1060
-     */
1061
-    public function reset_instance()
1062
-    {
1063
-        $this->clear_session();
1064
-        self::$_instance = null;
1065
-    }
1066
-
1067
-
1068
-
1069
-    public function configure_garbage_collection_filters()
1070
-    {
1071
-        // run old filter we had for controlling session cleanup
1072
-        $expired_session_transient_delete_query_limit = absint(
1073
-            apply_filters(
1074
-                'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit',
1075
-                50
1076
-            )
1077
-        );
1078
-        // is there a value? or one that is different than the default 50 records?
1079
-        if ($expired_session_transient_delete_query_limit === 0) {
1080
-            // hook into TransientCacheStorage in case Session cleanup was turned off
1081
-            add_filter('FHEE__TransientCacheStorage__transient_cleanup_schedule', '__return_zero');
1082
-        } elseif ($expired_session_transient_delete_query_limit !== 50) {
1083
-            // or use that for the new transient cleanup query limit
1084
-            add_filter(
1085
-                'FHEE__TransientCacheStorage__clearExpiredTransients__limit',
1086
-                function () use ($expired_session_transient_delete_query_limit)
1087
-                {
1088
-                    return $expired_session_transient_delete_query_limit;
1089
-                }
1090
-            );
1091
-        }
1092
-    }
1093
-
1094
-
1095
-
1096
-    /**
1097
-     * @see http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset/21389439#10152996
1098
-     * @param $data1
1099
-     * @return string
1100
-     */
1101
-    private function find_serialize_error($data1)
1102
-    {
1103
-        $error = '<pre>';
1104
-        $data2 = preg_replace_callback(
1105
-            '!s:(\d+):"(.*?)";!',
1106
-            function ($match)
1107
-            {
1108
-                return ($match[1] === strlen($match[2]))
1109
-                    ? $match[0]
1110
-                    : 's:'
1111
-                      . strlen($match[2])
1112
-                      . ':"'
1113
-                      . $match[2]
1114
-                      . '";';
1115
-            },
1116
-            $data1
1117
-        );
1118
-        $max   = (strlen($data1) > strlen($data2)) ? strlen($data1) : strlen($data2);
1119
-        $error .= $data1 . PHP_EOL;
1120
-        $error .= $data2 . PHP_EOL;
1121
-        for ($i = 0; $i < $max; $i++) {
1122
-            if (@$data1[ $i ] !== @$data2[ $i ]) {
1123
-                $error  .= 'Difference ' . @$data1[ $i ] . ' != ' . @$data2[ $i ] . PHP_EOL;
1124
-                $error  .= "\t-> ORD number " . ord(@$data1[ $i ]) . ' != ' . ord(@$data2[ $i ]) . PHP_EOL;
1125
-                $error  .= "\t-> Line Number = $i" . PHP_EOL;
1126
-                $start  = ($i - 20);
1127
-                $start  = ($start < 0) ? 0 : $start;
1128
-                $length = 40;
1129
-                $point  = $max - $i;
1130
-                if ($point < 20) {
1131
-                    $rlength = 1;
1132
-                    $rpoint  = -$point;
1133
-                } else {
1134
-                    $rpoint  = $length - 20;
1135
-                    $rlength = 1;
1136
-                }
1137
-                $error .= "\t-> Section Data1  = ";
1138
-                $error .= substr_replace(
1139
-                    substr($data1, $start, $length),
1140
-                    "<b style=\"color:green\">{$data1[ $i ]}</b>",
1141
-                    $rpoint,
1142
-                    $rlength
1143
-                );
1144
-                $error .= PHP_EOL;
1145
-                $error .= "\t-> Section Data2  = ";
1146
-                $error .= substr_replace(
1147
-                    substr($data2, $start, $length),
1148
-                    "<b style=\"color:red\">{$data2[ $i ]}</b>",
1149
-                    $rpoint,
1150
-                    $rlength
1151
-                );
1152
-                $error .= PHP_EOL;
1153
-            }
1154
-        }
1155
-        $error .= '</pre>';
1156
-        return $error;
1157
-    }
1158
-
1159
-
1160
-    /**
1161
-     * Saves an  array of settings used for configuring aspects of session behaviour
1162
-     *
1163
-     * @param array $updated_settings
1164
-     */
1165
-    private function updateSessionSettings(array $updated_settings = array())
1166
-    {
1167
-        // add existing settings, but only if not included in incoming $updated_settings array
1168
-        $updated_settings += get_option(EE_Session::OPTION_NAME_SETTINGS, array());
1169
-        update_option(EE_Session::OPTION_NAME_SETTINGS, $updated_settings);
1170
-    }
1171
-
1172
-
1173
-    /**
1174
-     * garbage_collection
1175
-     */
1176
-    public function garbageCollection()
1177
-    {
1178
-        // only perform during regular requests if last garbage collection was over an hour ago
1179
-        if (! (defined('DOING_AJAX') && DOING_AJAX) && (time() - HOUR_IN_SECONDS) >= $this->_last_gc) {
1180
-            $this->_last_gc = time();
1181
-            $this->updateSessionSettings(array('last_gc' => $this->_last_gc));
1182
-            /** @type WPDB $wpdb */
1183
-            global $wpdb;
1184
-            // filter the query limit. Set to 0 to turn off garbage collection
1185
-            $expired_session_transient_delete_query_limit = absint(
1186
-                apply_filters(
1187
-                    'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit',
1188
-                    50
1189
-                )
1190
-            );
1191
-            // non-zero LIMIT means take out the trash
1192
-            if ($expired_session_transient_delete_query_limit) {
1193
-                $session_key    = str_replace('_', '\_', EE_Session::session_id_prefix);
1194
-                $hash_check_key = str_replace('_', '\_', EE_Session::hash_check_prefix);
1195
-                // since transient expiration timestamps are set in the future, we can compare against NOW
1196
-                // but we only want to pick up any trash that's been around for more than a day
1197
-                $expiration = time() - DAY_IN_SECONDS;
1198
-                $SQL        = "
372
+	/**
373
+	 * reset_cart
374
+	 */
375
+	public function reset_cart()
376
+	{
377
+		do_action('AHEE__EE_Session__reset_cart__before_reset', $this);
378
+		$this->_session_data['cart'] = null;
379
+	}
380
+
381
+
382
+
383
+	/**
384
+	 * @return \EE_Cart
385
+	 */
386
+	public function cart()
387
+	{
388
+		return isset($this->_session_data['cart']) && $this->_session_data['cart'] instanceof EE_Cart
389
+			? $this->_session_data['cart']
390
+			: null;
391
+	}
392
+
393
+
394
+
395
+	/**
396
+	 * @param \EE_Checkout $checkout
397
+	 * @return bool
398
+	 */
399
+	public function set_checkout(EE_Checkout $checkout)
400
+	{
401
+		$this->_session_data['checkout'] = $checkout;
402
+		return true;
403
+	}
404
+
405
+
406
+
407
+	/**
408
+	 * reset_checkout
409
+	 */
410
+	public function reset_checkout()
411
+	{
412
+		do_action('AHEE__EE_Session__reset_checkout__before_reset', $this);
413
+		$this->_session_data['checkout'] = null;
414
+	}
415
+
416
+
417
+
418
+	/**
419
+	 * @return \EE_Checkout
420
+	 */
421
+	public function checkout()
422
+	{
423
+		return isset($this->_session_data['checkout']) && $this->_session_data['checkout'] instanceof EE_Checkout
424
+			? $this->_session_data['checkout']
425
+			: null;
426
+	}
427
+
428
+
429
+
430
+	/**
431
+	 * @param \EE_Transaction $transaction
432
+	 * @return bool
433
+	 * @throws EE_Error
434
+	 */
435
+	public function set_transaction(EE_Transaction $transaction)
436
+	{
437
+		// first remove the session from the transaction before we save the transaction in the session
438
+		$transaction->set_txn_session_data(null);
439
+		$this->_session_data['transaction'] = $transaction;
440
+		return true;
441
+	}
442
+
443
+
444
+
445
+	/**
446
+	 * reset_transaction
447
+	 */
448
+	public function reset_transaction()
449
+	{
450
+		do_action('AHEE__EE_Session__reset_transaction__before_reset', $this);
451
+		$this->_session_data['transaction'] = null;
452
+	}
453
+
454
+
455
+
456
+	/**
457
+	 * @return \EE_Transaction
458
+	 */
459
+	public function transaction()
460
+	{
461
+		return isset($this->_session_data['transaction'])
462
+			   && $this->_session_data['transaction'] instanceof EE_Transaction
463
+			? $this->_session_data['transaction']
464
+			: null;
465
+	}
466
+
467
+
468
+
469
+	/**
470
+	 * retrieve session data
471
+	 *
472
+	 * @access    public
473
+	 * @param null $key
474
+	 * @param bool $reset_cache
475
+	 * @return    array
476
+	 */
477
+	public function get_session_data($key = null, $reset_cache = false)
478
+	{
479
+		if ($reset_cache) {
480
+			$this->reset_cart();
481
+			$this->reset_checkout();
482
+			$this->reset_transaction();
483
+		}
484
+		if (! empty($key)) {
485
+			return isset($this->_session_data[ $key ]) ? $this->_session_data[ $key ] : null;
486
+		}
487
+		return $this->_session_data;
488
+	}
489
+
490
+
491
+
492
+	/**
493
+	 * set session data
494
+	 *
495
+	 * @access    public
496
+	 * @param    array $data
497
+	 * @return    TRUE on success, FALSE on fail
498
+	 */
499
+	public function set_session_data($data)
500
+	{
501
+
502
+		// nothing ??? bad data ??? go home!
503
+		if (empty($data) || ! is_array($data)) {
504
+			EE_Error::add_error(__('No session data or invalid session data was provided.', 'event_espresso'), __FILE__,
505
+				__FUNCTION__, __LINE__);
506
+			return false;
507
+		}
508
+		foreach ($data as $key => $value) {
509
+			if (isset($this->_default_session_vars[ $key ])) {
510
+				EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
511
+					'event_espresso'), $key), __FILE__, __FUNCTION__, __LINE__);
512
+				return false;
513
+			}
514
+			$this->_session_data[ $key ] = $value;
515
+		}
516
+		return true;
517
+	}
518
+
519
+
520
+
521
+	/**
522
+	 * @initiate session
523
+	 * @access   private
524
+	 * @return TRUE on success, FALSE on fail
525
+	 * @throws EE_Error
526
+	 * @throws InvalidArgumentException
527
+	 * @throws InvalidDataTypeException
528
+	 * @throws InvalidInterfaceException
529
+	 * @throws InvalidSessionDataException
530
+	 */
531
+	private function _espresso_session()
532
+	{
533
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
534
+		// check that session has started
535
+		if (session_id() === '') {
536
+			//starts a new session if one doesn't already exist, or re-initiates an existing one
537
+			session_start();
538
+		}
539
+		// get our modified session ID
540
+		$this->_sid = $this->_generate_session_id();
541
+		// and the visitors IP
542
+		$this->_ip_address = $this->request->ipAddress();
543
+		// set the "user agent"
544
+		$this->_user_agent = $this->request->userAgent();
545
+		// now let's retrieve what's in the db
546
+		$session_data = $this->_retrieve_session_data();
547
+		if (! empty($session_data)) {
548
+			// get the current time in UTC
549
+			$this->_time = $this->_time !== null ? $this->_time : time();
550
+			// and reset the session expiration
551
+			$this->_expiration = isset($session_data['expiration'])
552
+				? $session_data['expiration']
553
+				: $this->_time + $this->session_lifespan->inSeconds();
554
+		} else {
555
+			// set initial site access time and the session expiration
556
+			$this->_set_init_access_and_expiration();
557
+			// set referer
558
+			$this->_session_data['pages_visited'][ $this->_session_data['init_access'] ] = isset($_SERVER['HTTP_REFERER'])
559
+				? esc_attr($_SERVER['HTTP_REFERER'])
560
+				: '';
561
+			// no previous session = go back and create one (on top of the data above)
562
+			return false;
563
+		}
564
+		// now the user agent
565
+		if ($session_data['user_agent'] !== $this->_user_agent) {
566
+			return false;
567
+		}
568
+		// wait a minute... how old are you?
569
+		if ($this->_time > $this->_expiration) {
570
+			// yer too old fer me!
571
+			$this->_expired = true;
572
+			// wipe out everything that isn't a default session datum
573
+			$this->clear_session(__CLASS__, __FUNCTION__);
574
+		}
575
+		// make event espresso session data available to plugin
576
+		$this->_session_data = array_merge($this->_session_data, $session_data);
577
+		return true;
578
+	}
579
+
580
+
581
+
582
+	/**
583
+	 * _get_session_data
584
+	 * Retrieves the session data, and attempts to correct any encoding issues that can occur due to improperly setup
585
+	 * databases
586
+	 *
587
+	 * @return array
588
+	 * @throws EE_Error
589
+	 * @throws InvalidArgumentException
590
+	 * @throws InvalidSessionDataException
591
+	 * @throws InvalidDataTypeException
592
+	 * @throws InvalidInterfaceException
593
+	 */
594
+	protected function _retrieve_session_data()
595
+	{
596
+		$ssn_key = EE_Session::session_id_prefix . $this->_sid;
597
+		try {
598
+			// we're using WP's Transient API to store session data using the PHP session ID as the option name
599
+			$session_data = $this->cache_storage->get($ssn_key, false);
600
+			if (empty($session_data)) {
601
+				return array();
602
+			}
603
+			if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
604
+				$hash_check = $this->cache_storage->get(
605
+					EE_Session::hash_check_prefix . $this->_sid,
606
+					false
607
+				);
608
+				if ($hash_check && $hash_check !== md5($session_data)) {
609
+					EE_Error::add_error(
610
+						sprintf(
611
+							__(
612
+								'The stored data for session %1$s failed to pass a hash check and therefore appears to be invalid.',
613
+								'event_espresso'
614
+							),
615
+							EE_Session::session_id_prefix . $this->_sid
616
+						),
617
+						__FILE__, __FUNCTION__, __LINE__
618
+					);
619
+				}
620
+			}
621
+		} catch (Exception $e) {
622
+			// let's just eat that error for now and attempt to correct any corrupted data
623
+			global $wpdb;
624
+			$row          = $wpdb->get_row(
625
+				$wpdb->prepare(
626
+					"SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
627
+					'_transient_' . $ssn_key
628
+				)
629
+			);
630
+			$session_data = is_object($row) ? $row->option_value : null;
631
+			if ($session_data) {
632
+				$session_data = preg_replace_callback(
633
+					'!s:(d+):"(.*?)";!',
634
+					function ($match)
635
+					{
636
+						return $match[1] === strlen($match[2])
637
+							? $match[0]
638
+							: 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
639
+					},
640
+					$session_data
641
+				);
642
+			}
643
+			$session_data = maybe_unserialize($session_data);
644
+		}
645
+		// in case the data is encoded... try to decode it
646
+		$session_data = $this->encryption instanceof EE_Encryption
647
+			? $this->encryption->base64_string_decode($session_data)
648
+			: $session_data;
649
+		if (! is_array($session_data)) {
650
+			try {
651
+				$session_data = maybe_unserialize($session_data);
652
+			} catch (Exception $e) {
653
+				$msg = esc_html__(
654
+					'An error occurred while attempting to unserialize the session data.',
655
+					'event_espresso'
656
+				);
657
+				$msg .= WP_DEBUG
658
+					? '<br><pre>'
659
+					  . print_r($session_data, true)
660
+					  . '</pre><br>'
661
+					  . $this->find_serialize_error($session_data)
662
+					: '';
663
+				$this->cache_storage->delete(EE_Session::session_id_prefix . $this->_sid);
664
+				throw new InvalidSessionDataException($msg, 0, $e);
665
+			}
666
+		}
667
+		// just a check to make sure the session array is indeed an array
668
+		if (! is_array($session_data)) {
669
+			// no?!?! then something is wrong
670
+			$msg = esc_html__(
671
+				'The session data is missing, invalid, or corrupted.',
672
+				'event_espresso'
673
+			);
674
+			$msg .= WP_DEBUG
675
+				? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
676
+				: '';
677
+			$this->cache_storage->delete(EE_Session::session_id_prefix . $this->_sid);
678
+			throw new InvalidSessionDataException($msg);
679
+		}
680
+		if (isset($session_data['transaction']) && absint($session_data['transaction']) !== 0) {
681
+			$session_data['transaction'] = EEM_Transaction::instance()->get_one_by_ID(
682
+				$session_data['transaction']
683
+			);
684
+		}
685
+		return $session_data;
686
+	}
687
+
688
+
689
+
690
+	/**
691
+	 * _generate_session_id
692
+	 * Retrieves the PHP session id either directly from the PHP session,
693
+	 * or from the $_REQUEST array if it was passed in from an AJAX request.
694
+	 * The session id is then salted and hashed (mmm sounds tasty)
695
+	 * so that it can be safely used as a $_REQUEST param
696
+	 *
697
+	 * @return string
698
+	 */
699
+	protected function _generate_session_id()
700
+	{
701
+		// check if the SID was passed explicitly, otherwise get from session, then add salt and hash it to reduce length
702
+		if (isset($_REQUEST['EESID'])) {
703
+			$session_id = sanitize_text_field($_REQUEST['EESID']);
704
+		} else {
705
+			$session_id = md5(session_id() . get_current_blog_id() . $this->_get_sid_salt());
706
+		}
707
+		return apply_filters('FHEE__EE_Session___generate_session_id__session_id', $session_id);
708
+	}
709
+
710
+
711
+
712
+	/**
713
+	 * _get_sid_salt
714
+	 *
715
+	 * @return string
716
+	 */
717
+	protected function _get_sid_salt()
718
+	{
719
+		// was session id salt already saved to db ?
720
+		if (empty($this->_sid_salt)) {
721
+			// no?  then maybe use WP defined constant
722
+			if (defined('AUTH_SALT')) {
723
+				$this->_sid_salt = AUTH_SALT;
724
+			}
725
+			// if salt doesn't exist or is too short
726
+			if (strlen($this->_sid_salt) < 32) {
727
+				// create a new one
728
+				$this->_sid_salt = wp_generate_password(64);
729
+			}
730
+			// and save it as a permanent session setting
731
+			$this->updateSessionSettings(array('sid_salt' => $this->_sid_salt));
732
+		}
733
+		return $this->_sid_salt;
734
+	}
735
+
736
+
737
+
738
+	/**
739
+	 * _set_init_access_and_expiration
740
+	 *
741
+	 * @return void
742
+	 */
743
+	protected function _set_init_access_and_expiration()
744
+	{
745
+		$this->_time       = time();
746
+		$this->_expiration = $this->_time + $this->session_lifespan->inSeconds();
747
+		// set initial site access time
748
+		$this->_session_data['init_access'] = $this->_time;
749
+		// and the session expiration
750
+		$this->_session_data['expiration'] = $this->_expiration;
751
+	}
752
+
753
+
754
+
755
+	/**
756
+	 * @update session data  prior to saving to the db
757
+	 * @access public
758
+	 * @param bool $new_session
759
+	 * @return TRUE on success, FALSE on fail
760
+	 * @throws EE_Error
761
+	 * @throws InvalidArgumentException
762
+	 * @throws InvalidDataTypeException
763
+	 * @throws InvalidInterfaceException
764
+	 */
765
+	public function update($new_session = false)
766
+	{
767
+		$this->_session_data = $this->_session_data !== null
768
+							   && is_array($this->_session_data)
769
+							   && isset($this->_session_data['id'])
770
+			? $this->_session_data
771
+			: array();
772
+		if (empty($this->_session_data)) {
773
+			$this->_set_defaults();
774
+		}
775
+		$session_data = array();
776
+		foreach ($this->_session_data as $key => $value) {
777
+
778
+			switch ($key) {
779
+
780
+				case 'id' :
781
+					// session ID
782
+					$session_data['id'] = $this->_sid;
783
+					break;
784
+				case 'ip_address' :
785
+					// visitor ip address
786
+					$session_data['ip_address'] = $this->request->ipAddress();
787
+					break;
788
+				case 'user_agent' :
789
+					// visitor user_agent
790
+					$session_data['user_agent'] = $this->_user_agent;
791
+					break;
792
+				case 'init_access' :
793
+					$session_data['init_access'] = absint($value);
794
+					break;
795
+				case 'last_access' :
796
+					// current access time
797
+					$session_data['last_access'] = $this->_time;
798
+					break;
799
+				case 'expiration' :
800
+					// when the session expires
801
+					$session_data['expiration'] = ! empty($this->_expiration)
802
+						? $this->_expiration
803
+						: $session_data['init_access'] + $this->session_lifespan->inSeconds();
804
+					break;
805
+				case 'user_id' :
806
+					// current user if logged in
807
+					$session_data['user_id'] = $this->_wp_user_id();
808
+					break;
809
+				case 'pages_visited' :
810
+					$page_visit = $this->_get_page_visit();
811
+					if ($page_visit) {
812
+						// set pages visited where the first will be the http referrer
813
+						$this->_session_data['pages_visited'][ $this->_time ] = $page_visit;
814
+						// we'll only save the last 10 page visits.
815
+						$session_data['pages_visited'] = array_slice($this->_session_data['pages_visited'], -10);
816
+					}
817
+					break;
818
+				default :
819
+					// carry any other data over
820
+					$session_data[ $key ] = $this->_session_data[ $key ];
821
+			}
822
+		}
823
+		$this->_session_data = $session_data;
824
+		// creating a new session does not require saving to the db just yet
825
+		if (! $new_session) {
826
+			// ready? let's save
827
+			if ($this->_save_session_to_db()) {
828
+				return true;
829
+			}
830
+			return false;
831
+		}
832
+		// meh, why not?
833
+		return true;
834
+	}
835
+
836
+
837
+
838
+	/**
839
+	 * @create session data array
840
+	 * @access public
841
+	 * @return bool
842
+	 * @throws EE_Error
843
+	 * @throws InvalidArgumentException
844
+	 * @throws InvalidDataTypeException
845
+	 * @throws InvalidInterfaceException
846
+	 */
847
+	private function _create_espresso_session()
848
+	{
849
+		do_action('AHEE_log', __CLASS__, __FUNCTION__, '');
850
+		// use the update function for now with $new_session arg set to TRUE
851
+		return $this->update(true) ? true : false;
852
+	}
853
+
854
+
855
+
856
+	/**
857
+	 * _save_session_to_db
858
+	 *
859
+	 * @param bool $clear_session
860
+	 * @return string
861
+	 * @throws EE_Error
862
+	 * @throws InvalidArgumentException
863
+	 * @throws InvalidDataTypeException
864
+	 * @throws InvalidInterfaceException
865
+	 */
866
+	private function _save_session_to_db($clear_session = false)
867
+	{
868
+		// don't save sessions for crawlers
869
+		// and unless we're deleting the session data, don't save anything if there isn't a cart
870
+		if ($this->request->isBot() || (! $clear_session && ! $this->cart() instanceof EE_Cart)) {
871
+			return false;
872
+		}
873
+		$transaction = $this->transaction();
874
+		if ($transaction instanceof EE_Transaction) {
875
+			if (! $transaction->ID()) {
876
+				$transaction->save();
877
+			}
878
+			$this->_session_data['transaction'] = $transaction->ID();
879
+		}
880
+		// then serialize all of our session data
881
+		$session_data = serialize($this->_session_data);
882
+		// do we need to also encode it to avoid corrupted data when saved to the db?
883
+		$session_data = $this->_use_encryption
884
+			? $this->encryption->base64_string_encode($session_data)
885
+			: $session_data;
886
+		// maybe save hash check
887
+		if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
888
+			$this->cache_storage->add(
889
+				EE_Session::hash_check_prefix . $this->_sid,
890
+				md5($session_data),
891
+				$this->session_lifespan->inSeconds()
892
+			);
893
+		}
894
+		// we're using the Transient API for storing session data,
895
+		return $this->cache_storage->add(
896
+			EE_Session::session_id_prefix . $this->_sid,
897
+			$session_data,
898
+			$this->session_lifespan->inSeconds()
899
+		);
900
+	}
901
+
902
+
903
+	/**
904
+	 * @get    the full page request the visitor is accessing
905
+	 * @access public
906
+	 * @return string
907
+	 */
908
+	public function _get_page_visit()
909
+	{
910
+		$page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
911
+		// check for request url
912
+		if (isset($_SERVER['REQUEST_URI'])) {
913
+			$http_host   = '';
914
+			$page_id     = '?';
915
+			$e_reg       = '';
916
+			$request_uri = esc_url($_SERVER['REQUEST_URI']);
917
+			$ru_bits     = explode('?', $request_uri);
918
+			$request_uri = $ru_bits[0];
919
+			// check for and grab host as well
920
+			if (isset($_SERVER['HTTP_HOST'])) {
921
+				$http_host = esc_url($_SERVER['HTTP_HOST']);
922
+			}
923
+			// check for page_id in SERVER REQUEST
924
+			if (isset($_REQUEST['page_id'])) {
925
+				// rebuild $e_reg without any of the extra parameters
926
+				$page_id = '?page_id=' . esc_attr($_REQUEST['page_id']) . '&amp;';
927
+			}
928
+			// check for $e_reg in SERVER REQUEST
929
+			if (isset($_REQUEST['ee'])) {
930
+				// rebuild $e_reg without any of the extra parameters
931
+				$e_reg = 'ee=' . esc_attr($_REQUEST['ee']);
932
+			}
933
+			$page_visit = rtrim($http_host . $request_uri . $page_id . $e_reg, '?');
934
+		}
935
+		return $page_visit !== home_url('/wp-admin/admin-ajax.php') ? $page_visit : '';
936
+	}
937
+
938
+
939
+
940
+	/**
941
+	 * @the    current wp user id
942
+	 * @access public
943
+	 * @return int
944
+	 */
945
+	public function _wp_user_id()
946
+	{
947
+		// if I need to explain the following lines of code, then you shouldn't be looking at this!
948
+		$this->_wp_user_id = get_current_user_id();
949
+		return $this->_wp_user_id;
950
+	}
951
+
952
+
953
+
954
+	/**
955
+	 * Clear EE_Session data
956
+	 *
957
+	 * @access public
958
+	 * @param string $class
959
+	 * @param string $function
960
+	 * @return void
961
+	 * @throws EE_Error
962
+	 * @throws InvalidArgumentException
963
+	 * @throws InvalidDataTypeException
964
+	 * @throws InvalidInterfaceException
965
+	 */
966
+	public function clear_session($class = '', $function = '')
967
+	{
968
+		do_action('AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' . $function . '()');
969
+		$this->reset_cart();
970
+		$this->reset_checkout();
971
+		$this->reset_transaction();
972
+		// wipe out everything that isn't a default session datum
973
+		$this->reset_data(array_keys($this->_session_data));
974
+		// reset initial site access time and the session expiration
975
+		$this->_set_init_access_and_expiration();
976
+		$this->_save_session_to_db(true);
977
+	}
978
+
979
+
980
+
981
+	/**
982
+	 * @resets all non-default session vars
983
+	 * @access public
984
+	 * @param array|mixed $data_to_reset
985
+	 * @param bool        $show_all_notices
986
+	 * @return TRUE on success, FALSE on fail
987
+	 */
988
+	public function reset_data($data_to_reset = array(), $show_all_notices = false)
989
+	{
990
+		// if $data_to_reset is not in an array, then put it in one
991
+		if (! is_array($data_to_reset)) {
992
+			$data_to_reset = array($data_to_reset);
993
+		}
994
+		// nothing ??? go home!
995
+		if (empty($data_to_reset)) {
996
+			EE_Error::add_error(__('No session data could be reset, because no session var name was provided.',
997
+				'event_espresso'), __FILE__, __FUNCTION__, __LINE__);
998
+			return false;
999
+		}
1000
+		$return_value = true;
1001
+		// since $data_to_reset is an array, cycle through the values
1002
+		foreach ($data_to_reset as $reset) {
1003
+
1004
+			// first check to make sure it is a valid session var
1005
+			if (isset($this->_session_data[ $reset ])) {
1006
+				// then check to make sure it is not a default var
1007
+				if (! array_key_exists($reset, $this->_default_session_vars)) {
1008
+					// remove session var
1009
+					unset($this->_session_data[ $reset ]);
1010
+					if ($show_all_notices) {
1011
+						EE_Error::add_success(sprintf(__('The session variable %s was removed.', 'event_espresso'),
1012
+							$reset), __FILE__, __FUNCTION__, __LINE__);
1013
+					}
1014
+				} else {
1015
+					// yeeeeeeeeerrrrrrrrrrr OUT !!!!
1016
+					if ($show_all_notices) {
1017
+						EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
1018
+							'event_espresso'), $reset), __FILE__, __FUNCTION__, __LINE__);
1019
+					}
1020
+					$return_value = false;
1021
+				}
1022
+			} elseif ($show_all_notices) {
1023
+				// oops! that session var does not exist!
1024
+				EE_Error::add_error(sprintf(__('The session item provided, %s, is invalid or does not exist.',
1025
+					'event_espresso'), $reset), __FILE__, __FUNCTION__, __LINE__);
1026
+				$return_value = false;
1027
+			}
1028
+		} // end of foreach
1029
+		return $return_value;
1030
+	}
1031
+
1032
+
1033
+
1034
+	/**
1035
+	 *   wp_loaded
1036
+	 *
1037
+	 * @access public
1038
+	 * @throws EE_Error
1039
+	 * @throws InvalidDataTypeException
1040
+	 * @throws InvalidInterfaceException
1041
+	 * @throws InvalidArgumentException
1042
+	 */
1043
+	public function wp_loaded()
1044
+	{
1045
+		if ($this->request->requestParamIsSet('clear_session')) {
1046
+			$this->clear_session(__CLASS__, __FUNCTION__);
1047
+		}
1048
+	}
1049
+
1050
+
1051
+
1052
+	/**
1053
+	 * Used to reset the entire object (for tests).
1054
+	 *
1055
+	 * @since 4.3.0
1056
+	 * @throws EE_Error
1057
+	 * @throws InvalidDataTypeException
1058
+	 * @throws InvalidInterfaceException
1059
+	 * @throws InvalidArgumentException
1060
+	 */
1061
+	public function reset_instance()
1062
+	{
1063
+		$this->clear_session();
1064
+		self::$_instance = null;
1065
+	}
1066
+
1067
+
1068
+
1069
+	public function configure_garbage_collection_filters()
1070
+	{
1071
+		// run old filter we had for controlling session cleanup
1072
+		$expired_session_transient_delete_query_limit = absint(
1073
+			apply_filters(
1074
+				'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit',
1075
+				50
1076
+			)
1077
+		);
1078
+		// is there a value? or one that is different than the default 50 records?
1079
+		if ($expired_session_transient_delete_query_limit === 0) {
1080
+			// hook into TransientCacheStorage in case Session cleanup was turned off
1081
+			add_filter('FHEE__TransientCacheStorage__transient_cleanup_schedule', '__return_zero');
1082
+		} elseif ($expired_session_transient_delete_query_limit !== 50) {
1083
+			// or use that for the new transient cleanup query limit
1084
+			add_filter(
1085
+				'FHEE__TransientCacheStorage__clearExpiredTransients__limit',
1086
+				function () use ($expired_session_transient_delete_query_limit)
1087
+				{
1088
+					return $expired_session_transient_delete_query_limit;
1089
+				}
1090
+			);
1091
+		}
1092
+	}
1093
+
1094
+
1095
+
1096
+	/**
1097
+	 * @see http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset/21389439#10152996
1098
+	 * @param $data1
1099
+	 * @return string
1100
+	 */
1101
+	private function find_serialize_error($data1)
1102
+	{
1103
+		$error = '<pre>';
1104
+		$data2 = preg_replace_callback(
1105
+			'!s:(\d+):"(.*?)";!',
1106
+			function ($match)
1107
+			{
1108
+				return ($match[1] === strlen($match[2]))
1109
+					? $match[0]
1110
+					: 's:'
1111
+					  . strlen($match[2])
1112
+					  . ':"'
1113
+					  . $match[2]
1114
+					  . '";';
1115
+			},
1116
+			$data1
1117
+		);
1118
+		$max   = (strlen($data1) > strlen($data2)) ? strlen($data1) : strlen($data2);
1119
+		$error .= $data1 . PHP_EOL;
1120
+		$error .= $data2 . PHP_EOL;
1121
+		for ($i = 0; $i < $max; $i++) {
1122
+			if (@$data1[ $i ] !== @$data2[ $i ]) {
1123
+				$error  .= 'Difference ' . @$data1[ $i ] . ' != ' . @$data2[ $i ] . PHP_EOL;
1124
+				$error  .= "\t-> ORD number " . ord(@$data1[ $i ]) . ' != ' . ord(@$data2[ $i ]) . PHP_EOL;
1125
+				$error  .= "\t-> Line Number = $i" . PHP_EOL;
1126
+				$start  = ($i - 20);
1127
+				$start  = ($start < 0) ? 0 : $start;
1128
+				$length = 40;
1129
+				$point  = $max - $i;
1130
+				if ($point < 20) {
1131
+					$rlength = 1;
1132
+					$rpoint  = -$point;
1133
+				} else {
1134
+					$rpoint  = $length - 20;
1135
+					$rlength = 1;
1136
+				}
1137
+				$error .= "\t-> Section Data1  = ";
1138
+				$error .= substr_replace(
1139
+					substr($data1, $start, $length),
1140
+					"<b style=\"color:green\">{$data1[ $i ]}</b>",
1141
+					$rpoint,
1142
+					$rlength
1143
+				);
1144
+				$error .= PHP_EOL;
1145
+				$error .= "\t-> Section Data2  = ";
1146
+				$error .= substr_replace(
1147
+					substr($data2, $start, $length),
1148
+					"<b style=\"color:red\">{$data2[ $i ]}</b>",
1149
+					$rpoint,
1150
+					$rlength
1151
+				);
1152
+				$error .= PHP_EOL;
1153
+			}
1154
+		}
1155
+		$error .= '</pre>';
1156
+		return $error;
1157
+	}
1158
+
1159
+
1160
+	/**
1161
+	 * Saves an  array of settings used for configuring aspects of session behaviour
1162
+	 *
1163
+	 * @param array $updated_settings
1164
+	 */
1165
+	private function updateSessionSettings(array $updated_settings = array())
1166
+	{
1167
+		// add existing settings, but only if not included in incoming $updated_settings array
1168
+		$updated_settings += get_option(EE_Session::OPTION_NAME_SETTINGS, array());
1169
+		update_option(EE_Session::OPTION_NAME_SETTINGS, $updated_settings);
1170
+	}
1171
+
1172
+
1173
+	/**
1174
+	 * garbage_collection
1175
+	 */
1176
+	public function garbageCollection()
1177
+	{
1178
+		// only perform during regular requests if last garbage collection was over an hour ago
1179
+		if (! (defined('DOING_AJAX') && DOING_AJAX) && (time() - HOUR_IN_SECONDS) >= $this->_last_gc) {
1180
+			$this->_last_gc = time();
1181
+			$this->updateSessionSettings(array('last_gc' => $this->_last_gc));
1182
+			/** @type WPDB $wpdb */
1183
+			global $wpdb;
1184
+			// filter the query limit. Set to 0 to turn off garbage collection
1185
+			$expired_session_transient_delete_query_limit = absint(
1186
+				apply_filters(
1187
+					'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit',
1188
+					50
1189
+				)
1190
+			);
1191
+			// non-zero LIMIT means take out the trash
1192
+			if ($expired_session_transient_delete_query_limit) {
1193
+				$session_key    = str_replace('_', '\_', EE_Session::session_id_prefix);
1194
+				$hash_check_key = str_replace('_', '\_', EE_Session::hash_check_prefix);
1195
+				// since transient expiration timestamps are set in the future, we can compare against NOW
1196
+				// but we only want to pick up any trash that's been around for more than a day
1197
+				$expiration = time() - DAY_IN_SECONDS;
1198
+				$SQL        = "
1199 1199
                     SELECT option_name
1200 1200
                     FROM {$wpdb->options}
1201 1201
                     WHERE
@@ -1204,19 +1204,19 @@  discard block
 block discarded – undo
1204 1204
                     AND option_value < {$expiration}
1205 1205
                     LIMIT {$expired_session_transient_delete_query_limit}
1206 1206
                 ";
1207
-                // produces something like:
1208
-                // SELECT option_name FROM wp_options
1209
-                // WHERE ( option_name LIKE '\_transient\_timeout\_ee\_ssn\_%'
1210
-                // OR option_name LIKE '\_transient\_timeout\_ee\_shc\_%' )
1211
-                // AND option_value < 1508368198 LIMIT 50
1212
-                $expired_sessions = $wpdb->get_col($SQL);
1213
-                // valid results?
1214
-                if (! $expired_sessions instanceof WP_Error && ! empty($expired_sessions)) {
1215
-                    $this->cache_storage->deleteMany($expired_sessions, true);
1216
-                }
1217
-            }
1218
-        }
1219
-    }
1207
+				// produces something like:
1208
+				// SELECT option_name FROM wp_options
1209
+				// WHERE ( option_name LIKE '\_transient\_timeout\_ee\_ssn\_%'
1210
+				// OR option_name LIKE '\_transient\_timeout\_ee\_shc\_%' )
1211
+				// AND option_value < 1508368198 LIMIT 50
1212
+				$expired_sessions = $wpdb->get_col($SQL);
1213
+				// valid results?
1214
+				if (! $expired_sessions instanceof WP_Error && ! empty($expired_sessions)) {
1215
+					$this->cache_storage->deleteMany($expired_sessions, true);
1216
+				}
1217
+			}
1218
+		}
1219
+	}
1220 1220
 
1221 1221
 
1222 1222
 
Please login to merge, or discard this patch.
Spacing   +57 added lines, -57 removed lines patch added patch discarded remove patch
@@ -174,7 +174,7 @@  discard block
 block discarded – undo
174 174
         // check if class object is instantiated
175 175
         // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via:
176 176
         // add_filter( 'FHEE_load_EE_Session', '__return_false' );
177
-        if (! self::$_instance instanceof EE_Session && apply_filters('FHEE_load_EE_Session', true)) {
177
+        if ( ! self::$_instance instanceof EE_Session && apply_filters('FHEE_load_EE_Session', true)) {
178 178
             self::$_instance = new self(
179 179
                 $cache_storage,
180 180
                 $lifespan,
@@ -204,22 +204,22 @@  discard block
 block discarded – undo
204 204
         EE_Encryption $encryption = null
205 205
     ) {
206 206
         // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via: add_filter( 'FHEE_load_EE_Session', '__return_false' );
207
-        if (! apply_filters('FHEE_load_EE_Session', true)) {
207
+        if ( ! apply_filters('FHEE_load_EE_Session', true)) {
208 208
             return;
209 209
         }
210 210
         $this->session_lifespan = $lifespan;
211 211
         $this->request          = $request;
212 212
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
213
-        if (! defined('ESPRESSO_SESSION')) {
213
+        if ( ! defined('ESPRESSO_SESSION')) {
214 214
             define('ESPRESSO_SESSION', true);
215 215
         }
216 216
         // retrieve session options from db
217 217
         $session_settings = (array) get_option(EE_Session::OPTION_NAME_SETTINGS, array());
218
-        if (! empty($session_settings)) {
218
+        if ( ! empty($session_settings)) {
219 219
             // cycle though existing session options
220 220
             foreach ($session_settings as $var_name => $session_setting) {
221 221
                 // set values for class properties
222
-                $var_name          = '_' . $var_name;
222
+                $var_name          = '_'.$var_name;
223 223
                 $this->{$var_name} = $session_setting;
224 224
             }
225 225
         }
@@ -257,7 +257,7 @@  discard block
 block discarded – undo
257 257
     public function open_session()
258 258
     {
259 259
         // check for existing session and retrieve it from db
260
-        if (! $this->_espresso_session()) {
260
+        if ( ! $this->_espresso_session()) {
261 261
             // or just start a new one
262 262
             $this->_create_espresso_session();
263 263
         }
@@ -309,7 +309,7 @@  discard block
 block discarded – undo
309 309
      */
310 310
     public function extend_expiration($time = 0)
311 311
     {
312
-        $time              = $time ? $time : $this->extension();
312
+        $time = $time ? $time : $this->extension();
313 313
         $this->_expiration += absint($time);
314 314
     }
315 315
 
@@ -336,9 +336,9 @@  discard block
 block discarded – undo
336 336
         // set some defaults
337 337
         foreach ($this->_default_session_vars as $key => $default_var) {
338 338
             if (is_array($default_var)) {
339
-                $this->_session_data[ $key ] = array();
339
+                $this->_session_data[$key] = array();
340 340
             } else {
341
-                $this->_session_data[ $key ] = '';
341
+                $this->_session_data[$key] = '';
342 342
             }
343 343
         }
344 344
     }
@@ -481,8 +481,8 @@  discard block
 block discarded – undo
481 481
             $this->reset_checkout();
482 482
             $this->reset_transaction();
483 483
         }
484
-        if (! empty($key)) {
485
-            return isset($this->_session_data[ $key ]) ? $this->_session_data[ $key ] : null;
484
+        if ( ! empty($key)) {
485
+            return isset($this->_session_data[$key]) ? $this->_session_data[$key] : null;
486 486
         }
487 487
         return $this->_session_data;
488 488
     }
@@ -506,12 +506,12 @@  discard block
 block discarded – undo
506 506
             return false;
507 507
         }
508 508
         foreach ($data as $key => $value) {
509
-            if (isset($this->_default_session_vars[ $key ])) {
509
+            if (isset($this->_default_session_vars[$key])) {
510 510
                 EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
511 511
                     'event_espresso'), $key), __FILE__, __FUNCTION__, __LINE__);
512 512
                 return false;
513 513
             }
514
-            $this->_session_data[ $key ] = $value;
514
+            $this->_session_data[$key] = $value;
515 515
         }
516 516
         return true;
517 517
     }
@@ -544,7 +544,7 @@  discard block
 block discarded – undo
544 544
         $this->_user_agent = $this->request->userAgent();
545 545
         // now let's retrieve what's in the db
546 546
         $session_data = $this->_retrieve_session_data();
547
-        if (! empty($session_data)) {
547
+        if ( ! empty($session_data)) {
548 548
             // get the current time in UTC
549 549
             $this->_time = $this->_time !== null ? $this->_time : time();
550 550
             // and reset the session expiration
@@ -555,7 +555,7 @@  discard block
 block discarded – undo
555 555
             // set initial site access time and the session expiration
556 556
             $this->_set_init_access_and_expiration();
557 557
             // set referer
558
-            $this->_session_data['pages_visited'][ $this->_session_data['init_access'] ] = isset($_SERVER['HTTP_REFERER'])
558
+            $this->_session_data['pages_visited'][$this->_session_data['init_access']] = isset($_SERVER['HTTP_REFERER'])
559 559
                 ? esc_attr($_SERVER['HTTP_REFERER'])
560 560
                 : '';
561 561
             // no previous session = go back and create one (on top of the data above)
@@ -593,7 +593,7 @@  discard block
 block discarded – undo
593 593
      */
594 594
     protected function _retrieve_session_data()
595 595
     {
596
-        $ssn_key = EE_Session::session_id_prefix . $this->_sid;
596
+        $ssn_key = EE_Session::session_id_prefix.$this->_sid;
597 597
         try {
598 598
             // we're using WP's Transient API to store session data using the PHP session ID as the option name
599 599
             $session_data = $this->cache_storage->get($ssn_key, false);
@@ -602,7 +602,7 @@  discard block
 block discarded – undo
602 602
             }
603 603
             if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
604 604
                 $hash_check = $this->cache_storage->get(
605
-                    EE_Session::hash_check_prefix . $this->_sid,
605
+                    EE_Session::hash_check_prefix.$this->_sid,
606 606
                     false
607 607
                 );
608 608
                 if ($hash_check && $hash_check !== md5($session_data)) {
@@ -612,7 +612,7 @@  discard block
 block discarded – undo
612 612
                                 'The stored data for session %1$s failed to pass a hash check and therefore appears to be invalid.',
613 613
                                 'event_espresso'
614 614
                             ),
615
-                            EE_Session::session_id_prefix . $this->_sid
615
+                            EE_Session::session_id_prefix.$this->_sid
616 616
                         ),
617 617
                         __FILE__, __FUNCTION__, __LINE__
618 618
                     );
@@ -621,21 +621,21 @@  discard block
 block discarded – undo
621 621
         } catch (Exception $e) {
622 622
             // let's just eat that error for now and attempt to correct any corrupted data
623 623
             global $wpdb;
624
-            $row          = $wpdb->get_row(
624
+            $row = $wpdb->get_row(
625 625
                 $wpdb->prepare(
626 626
                     "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
627
-                    '_transient_' . $ssn_key
627
+                    '_transient_'.$ssn_key
628 628
                 )
629 629
             );
630 630
             $session_data = is_object($row) ? $row->option_value : null;
631 631
             if ($session_data) {
632 632
                 $session_data = preg_replace_callback(
633 633
                     '!s:(d+):"(.*?)";!',
634
-                    function ($match)
634
+                    function($match)
635 635
                     {
636 636
                         return $match[1] === strlen($match[2])
637 637
                             ? $match[0]
638
-                            : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
638
+                            : 's:'.strlen($match[2]).':"'.$match[2].'";';
639 639
                     },
640 640
                     $session_data
641 641
                 );
@@ -646,7 +646,7 @@  discard block
 block discarded – undo
646 646
         $session_data = $this->encryption instanceof EE_Encryption
647 647
             ? $this->encryption->base64_string_decode($session_data)
648 648
             : $session_data;
649
-        if (! is_array($session_data)) {
649
+        if ( ! is_array($session_data)) {
650 650
             try {
651 651
                 $session_data = maybe_unserialize($session_data);
652 652
             } catch (Exception $e) {
@@ -660,21 +660,21 @@  discard block
 block discarded – undo
660 660
                       . '</pre><br>'
661 661
                       . $this->find_serialize_error($session_data)
662 662
                     : '';
663
-                $this->cache_storage->delete(EE_Session::session_id_prefix . $this->_sid);
663
+                $this->cache_storage->delete(EE_Session::session_id_prefix.$this->_sid);
664 664
                 throw new InvalidSessionDataException($msg, 0, $e);
665 665
             }
666 666
         }
667 667
         // just a check to make sure the session array is indeed an array
668
-        if (! is_array($session_data)) {
668
+        if ( ! is_array($session_data)) {
669 669
             // no?!?! then something is wrong
670 670
             $msg = esc_html__(
671 671
                 'The session data is missing, invalid, or corrupted.',
672 672
                 'event_espresso'
673 673
             );
674 674
             $msg .= WP_DEBUG
675
-                ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
675
+                ? '<br><pre>'.print_r($session_data, true).'</pre><br>'.$this->find_serialize_error($session_data)
676 676
                 : '';
677
-            $this->cache_storage->delete(EE_Session::session_id_prefix . $this->_sid);
677
+            $this->cache_storage->delete(EE_Session::session_id_prefix.$this->_sid);
678 678
             throw new InvalidSessionDataException($msg);
679 679
         }
680 680
         if (isset($session_data['transaction']) && absint($session_data['transaction']) !== 0) {
@@ -702,7 +702,7 @@  discard block
 block discarded – undo
702 702
         if (isset($_REQUEST['EESID'])) {
703 703
             $session_id = sanitize_text_field($_REQUEST['EESID']);
704 704
         } else {
705
-            $session_id = md5(session_id() . get_current_blog_id() . $this->_get_sid_salt());
705
+            $session_id = md5(session_id().get_current_blog_id().$this->_get_sid_salt());
706 706
         }
707 707
         return apply_filters('FHEE__EE_Session___generate_session_id__session_id', $session_id);
708 708
     }
@@ -810,19 +810,19 @@  discard block
 block discarded – undo
810 810
                     $page_visit = $this->_get_page_visit();
811 811
                     if ($page_visit) {
812 812
                         // set pages visited where the first will be the http referrer
813
-                        $this->_session_data['pages_visited'][ $this->_time ] = $page_visit;
813
+                        $this->_session_data['pages_visited'][$this->_time] = $page_visit;
814 814
                         // we'll only save the last 10 page visits.
815 815
                         $session_data['pages_visited'] = array_slice($this->_session_data['pages_visited'], -10);
816 816
                     }
817 817
                     break;
818 818
                 default :
819 819
                     // carry any other data over
820
-                    $session_data[ $key ] = $this->_session_data[ $key ];
820
+                    $session_data[$key] = $this->_session_data[$key];
821 821
             }
822 822
         }
823 823
         $this->_session_data = $session_data;
824 824
         // creating a new session does not require saving to the db just yet
825
-        if (! $new_session) {
825
+        if ( ! $new_session) {
826 826
             // ready? let's save
827 827
             if ($this->_save_session_to_db()) {
828 828
                 return true;
@@ -867,12 +867,12 @@  discard block
 block discarded – undo
867 867
     {
868 868
         // don't save sessions for crawlers
869 869
         // and unless we're deleting the session data, don't save anything if there isn't a cart
870
-        if ($this->request->isBot() || (! $clear_session && ! $this->cart() instanceof EE_Cart)) {
870
+        if ($this->request->isBot() || ( ! $clear_session && ! $this->cart() instanceof EE_Cart)) {
871 871
             return false;
872 872
         }
873 873
         $transaction = $this->transaction();
874 874
         if ($transaction instanceof EE_Transaction) {
875
-            if (! $transaction->ID()) {
875
+            if ( ! $transaction->ID()) {
876 876
                 $transaction->save();
877 877
             }
878 878
             $this->_session_data['transaction'] = $transaction->ID();
@@ -886,14 +886,14 @@  discard block
 block discarded – undo
886 886
         // maybe save hash check
887 887
         if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
888 888
             $this->cache_storage->add(
889
-                EE_Session::hash_check_prefix . $this->_sid,
889
+                EE_Session::hash_check_prefix.$this->_sid,
890 890
                 md5($session_data),
891 891
                 $this->session_lifespan->inSeconds()
892 892
             );
893 893
         }
894 894
         // we're using the Transient API for storing session data,
895 895
         return $this->cache_storage->add(
896
-            EE_Session::session_id_prefix . $this->_sid,
896
+            EE_Session::session_id_prefix.$this->_sid,
897 897
             $session_data,
898 898
             $this->session_lifespan->inSeconds()
899 899
         );
@@ -907,7 +907,7 @@  discard block
 block discarded – undo
907 907
      */
908 908
     public function _get_page_visit()
909 909
     {
910
-        $page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
910
+        $page_visit = home_url('/').'wp-admin/admin-ajax.php';
911 911
         // check for request url
912 912
         if (isset($_SERVER['REQUEST_URI'])) {
913 913
             $http_host   = '';
@@ -923,14 +923,14 @@  discard block
 block discarded – undo
923 923
             // check for page_id in SERVER REQUEST
924 924
             if (isset($_REQUEST['page_id'])) {
925 925
                 // rebuild $e_reg without any of the extra parameters
926
-                $page_id = '?page_id=' . esc_attr($_REQUEST['page_id']) . '&amp;';
926
+                $page_id = '?page_id='.esc_attr($_REQUEST['page_id']).'&amp;';
927 927
             }
928 928
             // check for $e_reg in SERVER REQUEST
929 929
             if (isset($_REQUEST['ee'])) {
930 930
                 // rebuild $e_reg without any of the extra parameters
931
-                $e_reg = 'ee=' . esc_attr($_REQUEST['ee']);
931
+                $e_reg = 'ee='.esc_attr($_REQUEST['ee']);
932 932
             }
933
-            $page_visit = rtrim($http_host . $request_uri . $page_id . $e_reg, '?');
933
+            $page_visit = rtrim($http_host.$request_uri.$page_id.$e_reg, '?');
934 934
         }
935 935
         return $page_visit !== home_url('/wp-admin/admin-ajax.php') ? $page_visit : '';
936 936
     }
@@ -965,7 +965,7 @@  discard block
 block discarded – undo
965 965
      */
966 966
     public function clear_session($class = '', $function = '')
967 967
     {
968
-        do_action('AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' . $function . '()');
968
+        do_action('AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : '.$class.'::'.$function.'()');
969 969
         $this->reset_cart();
970 970
         $this->reset_checkout();
971 971
         $this->reset_transaction();
@@ -988,7 +988,7 @@  discard block
 block discarded – undo
988 988
     public function reset_data($data_to_reset = array(), $show_all_notices = false)
989 989
     {
990 990
         // if $data_to_reset is not in an array, then put it in one
991
-        if (! is_array($data_to_reset)) {
991
+        if ( ! is_array($data_to_reset)) {
992 992
             $data_to_reset = array($data_to_reset);
993 993
         }
994 994
         // nothing ??? go home!
@@ -1002,11 +1002,11 @@  discard block
 block discarded – undo
1002 1002
         foreach ($data_to_reset as $reset) {
1003 1003
 
1004 1004
             // first check to make sure it is a valid session var
1005
-            if (isset($this->_session_data[ $reset ])) {
1005
+            if (isset($this->_session_data[$reset])) {
1006 1006
                 // then check to make sure it is not a default var
1007
-                if (! array_key_exists($reset, $this->_default_session_vars)) {
1007
+                if ( ! array_key_exists($reset, $this->_default_session_vars)) {
1008 1008
                     // remove session var
1009
-                    unset($this->_session_data[ $reset ]);
1009
+                    unset($this->_session_data[$reset]);
1010 1010
                     if ($show_all_notices) {
1011 1011
                         EE_Error::add_success(sprintf(__('The session variable %s was removed.', 'event_espresso'),
1012 1012
                             $reset), __FILE__, __FUNCTION__, __LINE__);
@@ -1083,7 +1083,7 @@  discard block
 block discarded – undo
1083 1083
             // or use that for the new transient cleanup query limit
1084 1084
             add_filter(
1085 1085
                 'FHEE__TransientCacheStorage__clearExpiredTransients__limit',
1086
-                function () use ($expired_session_transient_delete_query_limit)
1086
+                function() use ($expired_session_transient_delete_query_limit)
1087 1087
                 {
1088 1088
                     return $expired_session_transient_delete_query_limit;
1089 1089
                 }
@@ -1103,7 +1103,7 @@  discard block
 block discarded – undo
1103 1103
         $error = '<pre>';
1104 1104
         $data2 = preg_replace_callback(
1105 1105
             '!s:(\d+):"(.*?)";!',
1106
-            function ($match)
1106
+            function($match)
1107 1107
             {
1108 1108
                 return ($match[1] === strlen($match[2]))
1109 1109
                     ? $match[0]
@@ -1115,14 +1115,14 @@  discard block
 block discarded – undo
1115 1115
             },
1116 1116
             $data1
1117 1117
         );
1118
-        $max   = (strlen($data1) > strlen($data2)) ? strlen($data1) : strlen($data2);
1119
-        $error .= $data1 . PHP_EOL;
1120
-        $error .= $data2 . PHP_EOL;
1118
+        $max = (strlen($data1) > strlen($data2)) ? strlen($data1) : strlen($data2);
1119
+        $error .= $data1.PHP_EOL;
1120
+        $error .= $data2.PHP_EOL;
1121 1121
         for ($i = 0; $i < $max; $i++) {
1122
-            if (@$data1[ $i ] !== @$data2[ $i ]) {
1123
-                $error  .= 'Difference ' . @$data1[ $i ] . ' != ' . @$data2[ $i ] . PHP_EOL;
1124
-                $error  .= "\t-> ORD number " . ord(@$data1[ $i ]) . ' != ' . ord(@$data2[ $i ]) . PHP_EOL;
1125
-                $error  .= "\t-> Line Number = $i" . PHP_EOL;
1122
+            if (@$data1[$i] !== @$data2[$i]) {
1123
+                $error  .= 'Difference '.@$data1[$i].' != '.@$data2[$i].PHP_EOL;
1124
+                $error  .= "\t-> ORD number ".ord(@$data1[$i]).' != '.ord(@$data2[$i]).PHP_EOL;
1125
+                $error  .= "\t-> Line Number = $i".PHP_EOL;
1126 1126
                 $start  = ($i - 20);
1127 1127
                 $start  = ($start < 0) ? 0 : $start;
1128 1128
                 $length = 40;
@@ -1137,7 +1137,7 @@  discard block
 block discarded – undo
1137 1137
                 $error .= "\t-> Section Data1  = ";
1138 1138
                 $error .= substr_replace(
1139 1139
                     substr($data1, $start, $length),
1140
-                    "<b style=\"color:green\">{$data1[ $i ]}</b>",
1140
+                    "<b style=\"color:green\">{$data1[$i]}</b>",
1141 1141
                     $rpoint,
1142 1142
                     $rlength
1143 1143
                 );
@@ -1145,7 +1145,7 @@  discard block
 block discarded – undo
1145 1145
                 $error .= "\t-> Section Data2  = ";
1146 1146
                 $error .= substr_replace(
1147 1147
                     substr($data2, $start, $length),
1148
-                    "<b style=\"color:red\">{$data2[ $i ]}</b>",
1148
+                    "<b style=\"color:red\">{$data2[$i]}</b>",
1149 1149
                     $rpoint,
1150 1150
                     $rlength
1151 1151
                 );
@@ -1176,7 +1176,7 @@  discard block
 block discarded – undo
1176 1176
     public function garbageCollection()
1177 1177
     {
1178 1178
         // only perform during regular requests if last garbage collection was over an hour ago
1179
-        if (! (defined('DOING_AJAX') && DOING_AJAX) && (time() - HOUR_IN_SECONDS) >= $this->_last_gc) {
1179
+        if ( ! (defined('DOING_AJAX') && DOING_AJAX) && (time() - HOUR_IN_SECONDS) >= $this->_last_gc) {
1180 1180
             $this->_last_gc = time();
1181 1181
             $this->updateSessionSettings(array('last_gc' => $this->_last_gc));
1182 1182
             /** @type WPDB $wpdb */
@@ -1211,7 +1211,7 @@  discard block
 block discarded – undo
1211 1211
                 // AND option_value < 1508368198 LIMIT 50
1212 1212
                 $expired_sessions = $wpdb->get_col($SQL);
1213 1213
                 // valid results?
1214
-                if (! $expired_sessions instanceof WP_Error && ! empty($expired_sessions)) {
1214
+                if ( ! $expired_sessions instanceof WP_Error && ! empty($expired_sessions)) {
1215 1215
                     $this->cache_storage->deleteMany($expired_sessions, true);
1216 1216
                 }
1217 1217
             }
Please login to merge, or discard this patch.
core/db_models/EEM_Base.model.php 2 patches
Spacing   +156 added lines, -156 removed lines patch added patch discarded remove patch
@@ -515,8 +515,8 @@  discard block
 block discarded – undo
515 515
     protected function __construct($timezone = null)
516 516
     {
517 517
         // check that the model has not been loaded too soon
518
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
519
-            throw new EE_Error (
518
+        if ( ! did_action('AHEE__EE_System__load_espresso_addons')) {
519
+            throw new EE_Error(
520 520
                 sprintf(
521 521
                     __('The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
522 522
                         'event_espresso'),
@@ -536,7 +536,7 @@  discard block
 block discarded – undo
536 536
          *
537 537
          * @var EE_Table_Base[] $_tables
538 538
          */
539
-        $this->_tables = (array)apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
539
+        $this->_tables = (array) apply_filters('FHEE__'.get_class($this).'__construct__tables', $this->_tables);
540 540
         foreach ($this->_tables as $table_alias => $table_obj) {
541 541
             /** @var $table_obj EE_Table_Base */
542 542
             $table_obj->_construct_finalize_with_alias($table_alias);
@@ -551,10 +551,10 @@  discard block
 block discarded – undo
551 551
          *
552 552
          * @param EE_Model_Field_Base[] $_fields
553 553
          */
554
-        $this->_fields = (array)apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
554
+        $this->_fields = (array) apply_filters('FHEE__'.get_class($this).'__construct__fields', $this->_fields);
555 555
         $this->_invalidate_field_caches();
556 556
         foreach ($this->_fields as $table_alias => $fields_for_table) {
557
-            if (! array_key_exists($table_alias, $this->_tables)) {
557
+            if ( ! array_key_exists($table_alias, $this->_tables)) {
558 558
                 throw new EE_Error(sprintf(__("Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
559 559
                     'event_espresso'), $table_alias, implode(",", $this->_fields)));
560 560
             }
@@ -582,7 +582,7 @@  discard block
 block discarded – undo
582 582
          *
583 583
          * @param EE_Model_Relation_Base[] $_model_relations
584 584
          */
585
-        $this->_model_relations = (array)apply_filters('FHEE__' . get_class($this) . '__construct__model_relations',
585
+        $this->_model_relations = (array) apply_filters('FHEE__'.get_class($this).'__construct__model_relations',
586 586
             $this->_model_relations);
587 587
         foreach ($this->_model_relations as $model_name => $relation_obj) {
588 588
             /** @var $relation_obj EE_Model_Relation_Base */
@@ -594,12 +594,12 @@  discard block
 block discarded – undo
594 594
         }
595 595
         $this->set_timezone($timezone);
596 596
         //finalize default where condition strategy, or set default
597
-        if (! $this->_default_where_conditions_strategy) {
597
+        if ( ! $this->_default_where_conditions_strategy) {
598 598
             //nothing was set during child constructor, so set default
599 599
             $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
600 600
         }
601 601
         $this->_default_where_conditions_strategy->_finalize_construct($this);
602
-        if (! $this->_minimum_where_conditions_strategy) {
602
+        if ( ! $this->_minimum_where_conditions_strategy) {
603 603
             //nothing was set during child constructor, so set default
604 604
             $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
605 605
         }
@@ -612,7 +612,7 @@  discard block
 block discarded – undo
612 612
         //initialize the standard cap restriction generators if none were specified by the child constructor
613 613
         if ($this->_cap_restriction_generators !== false) {
614 614
             foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
615
-                if (! isset($this->_cap_restriction_generators[$cap_context])) {
615
+                if ( ! isset($this->_cap_restriction_generators[$cap_context])) {
616 616
                     $this->_cap_restriction_generators[$cap_context] = apply_filters(
617 617
                         'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
618 618
                         new EE_Restriction_Generator_Protected(),
@@ -625,10 +625,10 @@  discard block
 block discarded – undo
625 625
         //if there are cap restriction generators, use them to make the default cap restrictions
626 626
         if ($this->_cap_restriction_generators !== false) {
627 627
             foreach ($this->_cap_restriction_generators as $context => $generator_object) {
628
-                if (! $generator_object) {
628
+                if ( ! $generator_object) {
629 629
                     continue;
630 630
                 }
631
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
631
+                if ( ! $generator_object instanceof EE_Restriction_Generator_Base) {
632 632
                     throw new EE_Error(
633 633
                         sprintf(
634 634
                             __('Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
@@ -639,12 +639,12 @@  discard block
 block discarded – undo
639 639
                     );
640 640
                 }
641 641
                 $action = $this->cap_action_for_context($context);
642
-                if (! $generator_object->construction_finalized()) {
642
+                if ( ! $generator_object->construction_finalized()) {
643 643
                     $generator_object->_construct_finalize($this, $action);
644 644
                 }
645 645
             }
646 646
         }
647
-        do_action('AHEE__' . get_class($this) . '__construct__end');
647
+        do_action('AHEE__'.get_class($this).'__construct__end');
648 648
     }
649 649
 
650 650
 
@@ -657,7 +657,7 @@  discard block
 block discarded – undo
657 657
      */
658 658
     public static function set_model_query_blog_id($blog_id = 0)
659 659
     {
660
-        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int)$blog_id : get_current_blog_id();
660
+        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int) $blog_id : get_current_blog_id();
661 661
     }
662 662
 
663 663
 
@@ -691,7 +691,7 @@  discard block
 block discarded – undo
691 691
     public static function instance($timezone = null)
692 692
     {
693 693
         // check if instance of Espresso_model already exists
694
-        if (! static::$_instance instanceof static) {
694
+        if ( ! static::$_instance instanceof static) {
695 695
             // instantiate Espresso_model
696 696
             static::$_instance = new static(
697 697
                 $timezone,
@@ -730,7 +730,7 @@  discard block
 block discarded – undo
730 730
             foreach ($r->getDefaultProperties() as $property => $value) {
731 731
                 //don't set instance to null like it was originally,
732 732
                 //but it's static anyways, and we're ignoring static properties (for now at least)
733
-                if (! isset($static_properties[$property])) {
733
+                if ( ! isset($static_properties[$property])) {
734 734
                     static::$_instance->{$property} = $value;
735 735
                 }
736 736
             }
@@ -754,7 +754,7 @@  discard block
 block discarded – undo
754 754
      */
755 755
     private static function getLoader()
756 756
     {
757
-        if(! EEM_Base::$loader instanceof LoaderInterface) {
757
+        if ( ! EEM_Base::$loader instanceof LoaderInterface) {
758 758
             EEM_Base::$loader = LoaderFactory::getLoader();
759 759
         }
760 760
         return EEM_Base::$loader;
@@ -774,7 +774,7 @@  discard block
 block discarded – undo
774 774
      */
775 775
     public function status_array($translated = false)
776 776
     {
777
-        if (! array_key_exists('Status', $this->_model_relations)) {
777
+        if ( ! array_key_exists('Status', $this->_model_relations)) {
778 778
             return array();
779 779
         }
780 780
         $model_name = $this->get_this_model_name();
@@ -977,17 +977,17 @@  discard block
 block discarded – undo
977 977
     public function wp_user_field_name()
978 978
     {
979 979
         try {
980
-            if (! empty($this->_model_chain_to_wp_user)) {
980
+            if ( ! empty($this->_model_chain_to_wp_user)) {
981 981
                 $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
982 982
                 $last_model_name = end($models_to_follow_to_wp_users);
983 983
                 $model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
984
-                $model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
984
+                $model_chain_to_wp_user = $this->_model_chain_to_wp_user.'.';
985 985
             } else {
986 986
                 $model_with_fk_to_wp_users = $this;
987 987
                 $model_chain_to_wp_user = '';
988 988
             }
989 989
             $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
990
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
990
+            return $model_chain_to_wp_user.$wp_user_field->get_name();
991 991
         } catch (EE_Error $e) {
992 992
             return false;
993 993
         }
@@ -1055,7 +1055,7 @@  discard block
 block discarded – undo
1055 1055
      */
1056 1056
     protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1057 1057
     {
1058
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);;
1058
+        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select); ;
1059 1059
         $model_query_info = $this->_create_model_query_info_carrier($query_params);
1060 1060
         $select_expressions = $columns_to_select === null
1061 1061
             ? $this->_construct_default_select_sql($model_query_info)
@@ -1063,11 +1063,11 @@  discard block
 block discarded – undo
1063 1063
         if ($this->_custom_selections instanceof CustomSelects) {
1064 1064
             $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1065 1065
             $select_expressions .= $select_expressions
1066
-                ? ', ' . $custom_expressions
1066
+                ? ', '.$custom_expressions
1067 1067
                 : $custom_expressions;
1068 1068
         }
1069 1069
 
1070
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1070
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1071 1071
         return $this->_do_wpdb_query('get_results', array($SQL, $output));
1072 1072
     }
1073 1073
 
@@ -1084,7 +1084,7 @@  discard block
 block discarded – undo
1084 1084
      */
1085 1085
     protected function getCustomSelection(array $query_params, $columns_to_select = null)
1086 1086
     {
1087
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1087
+        if ( ! isset($query_params['extra_selects']) && $columns_to_select === null) {
1088 1088
             return null;
1089 1089
         }
1090 1090
         $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
@@ -1133,7 +1133,7 @@  discard block
 block discarded – undo
1133 1133
         if (is_array($columns_to_select)) {
1134 1134
             $select_sql_array = array();
1135 1135
             foreach ($columns_to_select as $alias => $selection_and_datatype) {
1136
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1136
+                if ( ! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1137 1137
                     throw new EE_Error(
1138 1138
                         sprintf(
1139 1139
                             __(
@@ -1145,7 +1145,7 @@  discard block
 block discarded – undo
1145 1145
                         )
1146 1146
                     );
1147 1147
                 }
1148
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1148
+                if ( ! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1149 1149
                     throw new EE_Error(
1150 1150
                         sprintf(
1151 1151
                             esc_html__(
@@ -1217,7 +1217,7 @@  discard block
 block discarded – undo
1217 1217
      */
1218 1218
     public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1219 1219
     {
1220
-        if (! isset($query_params[0])) {
1220
+        if ( ! isset($query_params[0])) {
1221 1221
             $query_params[0] = array();
1222 1222
         }
1223 1223
         $conditions_from_id = $this->parse_index_primary_key_string($id);
@@ -1242,7 +1242,7 @@  discard block
 block discarded – undo
1242 1242
      */
1243 1243
     public function get_one($query_params = array())
1244 1244
     {
1245
-        if (! is_array($query_params)) {
1245
+        if ( ! is_array($query_params)) {
1246 1246
             EE_Error::doing_it_wrong('EEM_Base::get_one',
1247 1247
                 sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1248 1248
                     gettype($query_params)), '4.6.0');
@@ -1433,7 +1433,7 @@  discard block
 block discarded – undo
1433 1433
                 return array();
1434 1434
             }
1435 1435
         }
1436
-        if (! is_array($query_params)) {
1436
+        if ( ! is_array($query_params)) {
1437 1437
             EE_Error::doing_it_wrong('EEM_Base::_get_consecutive',
1438 1438
                 sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1439 1439
                     gettype($query_params)), '4.6.0');
@@ -1443,7 +1443,7 @@  discard block
 block discarded – undo
1443 1443
         $query_params[0][$field_to_order_by] = array($operand, $current_field_value);
1444 1444
         $query_params['limit'] = $limit;
1445 1445
         //set direction
1446
-        $incoming_orderby = isset($query_params['order_by']) ? (array)$query_params['order_by'] : array();
1446
+        $incoming_orderby = isset($query_params['order_by']) ? (array) $query_params['order_by'] : array();
1447 1447
         $query_params['order_by'] = $operand === '>'
1448 1448
             ? array($field_to_order_by => 'ASC') + $incoming_orderby
1449 1449
             : array($field_to_order_by => 'DESC') + $incoming_orderby;
@@ -1521,7 +1521,7 @@  discard block
 block discarded – undo
1521 1521
     {
1522 1522
         $field_settings = $this->field_settings_for($field_name);
1523 1523
         //if not a valid EE_Datetime_Field then throw error
1524
-        if (! $field_settings instanceof EE_Datetime_Field) {
1524
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1525 1525
             throw new EE_Error(sprintf(__('The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1526 1526
                 'event_espresso'), $field_name));
1527 1527
         }
@@ -1672,7 +1672,7 @@  discard block
 block discarded – undo
1672 1672
      */
1673 1673
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1674 1674
     {
1675
-        if (! is_array($query_params)) {
1675
+        if ( ! is_array($query_params)) {
1676 1676
             EE_Error::doing_it_wrong('EEM_Base::update',
1677 1677
                 sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1678 1678
                     gettype($query_params)), '4.6.0');
@@ -1694,7 +1694,7 @@  discard block
 block discarded – undo
1694 1694
          * @param EEM_Base $model           the model being queried
1695 1695
          * @param array    $query_params    see EEM_Base::get_all()
1696 1696
          */
1697
-        $fields_n_values = (array)apply_filters('FHEE__EEM_Base__update__fields_n_values', $fields_n_values, $this,
1697
+        $fields_n_values = (array) apply_filters('FHEE__EEM_Base__update__fields_n_values', $fields_n_values, $this,
1698 1698
             $query_params);
1699 1699
         //need to verify that, for any entry we want to update, there are entries in each secondary table.
1700 1700
         //to do that, for each table, verify that it's PK isn't null.
@@ -1708,7 +1708,7 @@  discard block
 block discarded – undo
1708 1708
         $wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1709 1709
         foreach ($wpdb_select_results as $wpdb_result) {
1710 1710
             // type cast stdClass as array
1711
-            $wpdb_result = (array)$wpdb_result;
1711
+            $wpdb_result = (array) $wpdb_result;
1712 1712
             //get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1713 1713
             if ($this->has_primary_key_field()) {
1714 1714
                 $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
@@ -1725,13 +1725,13 @@  discard block
 block discarded – undo
1725 1725
                     $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1726 1726
                     //if there is no private key for this table on the results, it means there's no entry
1727 1727
                     //in this table, right? so insert a row in the current table, using any fields available
1728
-                    if (! (array_key_exists($this_table_pk_column, $wpdb_result)
1728
+                    if ( ! (array_key_exists($this_table_pk_column, $wpdb_result)
1729 1729
                            && $wpdb_result[$this_table_pk_column])
1730 1730
                     ) {
1731 1731
                         $success = $this->_insert_into_specific_table($table_obj, $fields_n_values,
1732 1732
                             $main_table_pk_value);
1733 1733
                         //if we died here, report the error
1734
-                        if (! $success) {
1734
+                        if ( ! $success) {
1735 1735
                             return false;
1736 1736
                         }
1737 1737
                     }
@@ -1762,7 +1762,7 @@  discard block
 block discarded – undo
1762 1762
                     $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1763 1763
                 }
1764 1764
             }
1765
-            if (! $model_objs_affected_ids) {
1765
+            if ( ! $model_objs_affected_ids) {
1766 1766
                 //wait wait wait- if nothing was affected let's stop here
1767 1767
                 return 0;
1768 1768
             }
@@ -1789,7 +1789,7 @@  discard block
 block discarded – undo
1789 1789
                . $model_query_info->get_full_join_sql()
1790 1790
                . " SET "
1791 1791
                . $this->_construct_update_sql($fields_n_values)
1792
-               . $model_query_info->get_where_sql();//note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1792
+               . $model_query_info->get_where_sql(); //note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1793 1793
         $rows_affected = $this->_do_wpdb_query('query', array($SQL));
1794 1794
         /**
1795 1795
          * Action called after a model update call has been made.
@@ -1800,7 +1800,7 @@  discard block
 block discarded – undo
1800 1800
          * @param int      $rows_affected
1801 1801
          */
1802 1802
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1803
-        return $rows_affected;//how many supposedly got updated
1803
+        return $rows_affected; //how many supposedly got updated
1804 1804
     }
1805 1805
 
1806 1806
 
@@ -1828,7 +1828,7 @@  discard block
 block discarded – undo
1828 1828
         }
1829 1829
         $model_query_info = $this->_create_model_query_info_carrier($query_params);
1830 1830
         $select_expressions = $field->get_qualified_column();
1831
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1831
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1832 1832
         return $this->_do_wpdb_query('get_col', array($SQL));
1833 1833
     }
1834 1834
 
@@ -1846,7 +1846,7 @@  discard block
 block discarded – undo
1846 1846
     {
1847 1847
         $query_params['limit'] = 1;
1848 1848
         $col = $this->get_col($query_params, $field_to_select);
1849
-        if (! empty($col)) {
1849
+        if ( ! empty($col)) {
1850 1850
             return reset($col);
1851 1851
         }
1852 1852
         return null;
@@ -1877,7 +1877,7 @@  discard block
 block discarded – undo
1877 1877
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1878 1878
             $value_sql = $prepared_value === null ? 'NULL'
1879 1879
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1880
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1880
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1881 1881
         }
1882 1882
         return implode(",", $cols_n_values);
1883 1883
     }
@@ -2059,7 +2059,7 @@  discard block
 block discarded – undo
2059 2059
          * @param int      $rows_deleted
2060 2060
          */
2061 2061
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2062
-        return $rows_deleted;//how many supposedly got deleted
2062
+        return $rows_deleted; //how many supposedly got deleted
2063 2063
     }
2064 2064
 
2065 2065
 
@@ -2209,7 +2209,7 @@  discard block
 block discarded – undo
2209 2209
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2210 2210
                 //make sure we have unique $ids
2211 2211
                 $ids = array_unique($ids);
2212
-                $query[] = $column . ' IN(' . implode(',', $ids) . ')';
2212
+                $query[] = $column.' IN('.implode(',', $ids).')';
2213 2213
             }
2214 2214
             $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2215 2215
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2217,7 +2217,7 @@  discard block
 block discarded – undo
2217 2217
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2218 2218
                 $values_for_each_combined_primary_key_for_a_row = array();
2219 2219
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2220
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2220
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2221 2221
                 }
2222 2222
                 $ways_to_identify_a_row[] = '('
2223 2223
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2237,8 +2237,8 @@  discard block
 block discarded – undo
2237 2237
      */
2238 2238
     public function get_field_by_column($qualified_column_name)
2239 2239
     {
2240
-       foreach($this->field_settings(true) as $field_name => $field_obj){
2241
-           if($field_obj->get_qualified_column() === $qualified_column_name){
2240
+       foreach ($this->field_settings(true) as $field_name => $field_obj) {
2241
+           if ($field_obj->get_qualified_column() === $qualified_column_name) {
2242 2242
                return $field_obj;
2243 2243
            }
2244 2244
        }
@@ -2289,9 +2289,9 @@  discard block
 block discarded – undo
2289 2289
                 $column_to_count = '*';
2290 2290
             }
2291 2291
         }
2292
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2293
-        $SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2294
-        return (int)$this->_do_wpdb_query('get_var', array($SQL));
2292
+        $column_to_count = $distinct ? "DISTINCT ".$column_to_count : $column_to_count;
2293
+        $SQL = "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2294
+        return (int) $this->_do_wpdb_query('get_var', array($SQL));
2295 2295
     }
2296 2296
 
2297 2297
 
@@ -2313,14 +2313,14 @@  discard block
 block discarded – undo
2313 2313
             $field_obj = $this->get_primary_key_field();
2314 2314
         }
2315 2315
         $column_to_count = $field_obj->get_qualified_column();
2316
-        $SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2316
+        $SQL = "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2317 2317
         $return_value = $this->_do_wpdb_query('get_var', array($SQL));
2318 2318
         $data_type = $field_obj->get_wpdb_data_type();
2319 2319
         if ($data_type === '%d' || $data_type === '%s') {
2320
-            return (float)$return_value;
2320
+            return (float) $return_value;
2321 2321
         }
2322 2322
         //must be %f
2323
-        return (float)$return_value;
2323
+        return (float) $return_value;
2324 2324
     }
2325 2325
 
2326 2326
 
@@ -2340,13 +2340,13 @@  discard block
 block discarded – undo
2340 2340
         //if we're in maintenance mode level 2, DON'T run any queries
2341 2341
         //because level 2 indicates the database needs updating and
2342 2342
         //is probably out of sync with the code
2343
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2343
+        if ( ! EE_Maintenance_Mode::instance()->models_can_query()) {
2344 2344
             throw new EE_Error(sprintf(__("Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2345 2345
                 "event_espresso")));
2346 2346
         }
2347 2347
         /** @type WPDB $wpdb */
2348 2348
         global $wpdb;
2349
-        if (! method_exists($wpdb, $wpdb_method)) {
2349
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2350 2350
             throw new EE_Error(sprintf(__('There is no method named "%s" on Wordpress\' $wpdb object',
2351 2351
                 'event_espresso'), $wpdb_method));
2352 2352
         }
@@ -2358,7 +2358,7 @@  discard block
 block discarded – undo
2358 2358
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2359 2359
         if (WP_DEBUG) {
2360 2360
             $wpdb->show_errors($old_show_errors_value);
2361
-            if (! empty($wpdb->last_error)) {
2361
+            if ( ! empty($wpdb->last_error)) {
2362 2362
                 throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2363 2363
             }
2364 2364
             if ($result === false) {
@@ -2419,7 +2419,7 @@  discard block
 block discarded – undo
2419 2419
                     return $result;
2420 2420
                     break;
2421 2421
             }
2422
-            if (! empty($error_message)) {
2422
+            if ( ! empty($error_message)) {
2423 2423
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2424 2424
                 trigger_error($error_message);
2425 2425
             }
@@ -2495,11 +2495,11 @@  discard block
 block discarded – undo
2495 2495
      */
2496 2496
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2497 2497
     {
2498
-        return " FROM " . $model_query_info->get_full_join_sql() .
2499
-               $model_query_info->get_where_sql() .
2500
-               $model_query_info->get_group_by_sql() .
2501
-               $model_query_info->get_having_sql() .
2502
-               $model_query_info->get_order_by_sql() .
2498
+        return " FROM ".$model_query_info->get_full_join_sql().
2499
+               $model_query_info->get_where_sql().
2500
+               $model_query_info->get_group_by_sql().
2501
+               $model_query_info->get_having_sql().
2502
+               $model_query_info->get_order_by_sql().
2503 2503
                $model_query_info->get_limit_sql();
2504 2504
     }
2505 2505
 
@@ -2695,12 +2695,12 @@  discard block
 block discarded – undo
2695 2695
         $related_model = $this->get_related_model_obj($model_name);
2696 2696
         //we're just going to use the query params on the related model's normal get_all query,
2697 2697
         //except add a condition to say to match the current mod
2698
-        if (! isset($query_params['default_where_conditions'])) {
2698
+        if ( ! isset($query_params['default_where_conditions'])) {
2699 2699
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2700 2700
         }
2701 2701
         $this_model_name = $this->get_this_model_name();
2702 2702
         $this_pk_field_name = $this->get_primary_key_field()->get_name();
2703
-        $query_params[0][$this_model_name . "." . $this_pk_field_name] = $id_or_obj;
2703
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2704 2704
         return $related_model->count($query_params, $field_to_count, $distinct);
2705 2705
     }
2706 2706
 
@@ -2720,7 +2720,7 @@  discard block
 block discarded – undo
2720 2720
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2721 2721
     {
2722 2722
         $related_model = $this->get_related_model_obj($model_name);
2723
-        if (! is_array($query_params)) {
2723
+        if ( ! is_array($query_params)) {
2724 2724
             EE_Error::doing_it_wrong('EEM_Base::sum_related',
2725 2725
                 sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2726 2726
                     gettype($query_params)), '4.6.0');
@@ -2728,12 +2728,12 @@  discard block
 block discarded – undo
2728 2728
         }
2729 2729
         //we're just going to use the query params on the related model's normal get_all query,
2730 2730
         //except add a condition to say to match the current mod
2731
-        if (! isset($query_params['default_where_conditions'])) {
2731
+        if ( ! isset($query_params['default_where_conditions'])) {
2732 2732
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2733 2733
         }
2734 2734
         $this_model_name = $this->get_this_model_name();
2735 2735
         $this_pk_field_name = $this->get_primary_key_field()->get_name();
2736
-        $query_params[0][$this_model_name . "." . $this_pk_field_name] = $id_or_obj;
2736
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2737 2737
         return $related_model->sum($query_params, $field_to_sum);
2738 2738
     }
2739 2739
 
@@ -2786,7 +2786,7 @@  discard block
 block discarded – undo
2786 2786
                 $field_with_model_name = $field;
2787 2787
             }
2788 2788
         }
2789
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2789
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2790 2790
             throw new EE_Error(sprintf(__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2791 2791
                 $this->get_this_model_name()));
2792 2792
         }
@@ -2819,7 +2819,7 @@  discard block
 block discarded – undo
2819 2819
          * @param array    $fields_n_values keys are the fields and values are their new values
2820 2820
          * @param EEM_Base $model           the model used
2821 2821
          */
2822
-        $field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2822
+        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2823 2823
         if ($this->_satisfies_unique_indexes($field_n_values)) {
2824 2824
             $main_table = $this->_get_main_table();
2825 2825
             $new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
@@ -2926,7 +2926,7 @@  discard block
 block discarded – undo
2926 2926
         }
2927 2927
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2928 2928
             $uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2929
-            $query_params[0]['OR']['AND*' . $unique_index_name] = $uniqueness_where_params;
2929
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
2930 2930
         }
2931 2931
         //if there is nothing to base this search on, then we shouldn't find anything
2932 2932
         if (empty($query_params)) {
@@ -3012,7 +3012,7 @@  discard block
 block discarded – undo
3012 3012
             //its not the main table, so we should have already saved the main table's PK which we just inserted
3013 3013
             //so add the fk to the main table as a column
3014 3014
             $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3015
-            $format_for_insertion[] = '%d';//yes right now we're only allowing these foreign keys to be INTs
3015
+            $format_for_insertion[] = '%d'; //yes right now we're only allowing these foreign keys to be INTs
3016 3016
         }
3017 3017
         //insert the new entry
3018 3018
         $result = $this->_do_wpdb_query('insert',
@@ -3216,7 +3216,7 @@  discard block
 block discarded – undo
3216 3216
                     $query_info_carrier,
3217 3217
                     'group_by'
3218 3218
                 );
3219
-            } elseif (! empty ($query_params['group_by'])) {
3219
+            } elseif ( ! empty ($query_params['group_by'])) {
3220 3220
                 $this->_extract_related_model_info_from_query_param(
3221 3221
                     $query_params['group_by'],
3222 3222
                     $query_info_carrier,
@@ -3238,7 +3238,7 @@  discard block
 block discarded – undo
3238 3238
                     $query_info_carrier,
3239 3239
                     'order_by'
3240 3240
                 );
3241
-            } elseif (! empty($query_params['order_by'])) {
3241
+            } elseif ( ! empty($query_params['order_by'])) {
3242 3242
                 $this->_extract_related_model_info_from_query_param(
3243 3243
                     $query_params['order_by'],
3244 3244
                     $query_info_carrier,
@@ -3274,8 +3274,8 @@  discard block
 block discarded – undo
3274 3274
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3275 3275
         $query_param_type
3276 3276
     ) {
3277
-        if (! empty($sub_query_params)) {
3278
-            $sub_query_params = (array)$sub_query_params;
3277
+        if ( ! empty($sub_query_params)) {
3278
+            $sub_query_params = (array) $sub_query_params;
3279 3279
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3280 3280
                 //$param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3281 3281
                 $this->_extract_related_model_info_from_query_param($param, $model_query_info_carrier,
@@ -3286,7 +3286,7 @@  discard block
 block discarded – undo
3286 3286
                 //of array('Registration.TXN_ID'=>23)
3287 3287
                 $query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3288 3288
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3289
-                    if (! is_array($possibly_array_of_params)) {
3289
+                    if ( ! is_array($possibly_array_of_params)) {
3290 3290
                         throw new EE_Error(sprintf(__("You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3291 3291
                             "event_espresso"),
3292 3292
                             $param, $possibly_array_of_params));
@@ -3303,7 +3303,7 @@  discard block
 block discarded – undo
3303 3303
                     //then $possible_array_of_params looks something like array('<','DTT_sold',true)
3304 3304
                     //indicating that $possible_array_of_params[1] is actually a field name,
3305 3305
                     //from which we should extract query parameters!
3306
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3306
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3307 3307
                         throw new EE_Error(sprintf(__("Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3308 3308
                             "event_espresso"), $query_param_type, implode(",", $possibly_array_of_params)));
3309 3309
                     }
@@ -3333,8 +3333,8 @@  discard block
 block discarded – undo
3333 3333
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3334 3334
         $query_param_type
3335 3335
     ) {
3336
-        if (! empty($sub_query_params)) {
3337
-            if (! is_array($sub_query_params)) {
3336
+        if ( ! empty($sub_query_params)) {
3337
+            if ( ! is_array($sub_query_params)) {
3338 3338
                 throw new EE_Error(sprintf(__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3339 3339
                     $sub_query_params));
3340 3340
             }
@@ -3363,7 +3363,7 @@  discard block
 block discarded – undo
3363 3363
      */
3364 3364
     public function _create_model_query_info_carrier($query_params)
3365 3365
     {
3366
-        if (! is_array($query_params)) {
3366
+        if ( ! is_array($query_params)) {
3367 3367
             EE_Error::doing_it_wrong(
3368 3368
                 'EEM_Base::_create_model_query_info_carrier',
3369 3369
                 sprintf(
@@ -3439,7 +3439,7 @@  discard block
 block discarded – undo
3439 3439
         //set limit
3440 3440
         if (array_key_exists('limit', $query_params)) {
3441 3441
             if (is_array($query_params['limit'])) {
3442
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3442
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3443 3443
                     $e = sprintf(
3444 3444
                         __(
3445 3445
                             "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
@@ -3447,12 +3447,12 @@  discard block
 block discarded – undo
3447 3447
                         ),
3448 3448
                         http_build_query($query_params['limit'])
3449 3449
                     );
3450
-                    throw new EE_Error($e . "|" . $e);
3450
+                    throw new EE_Error($e."|".$e);
3451 3451
                 }
3452 3452
                 //they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3453
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3454
-            } elseif (! empty ($query_params['limit'])) {
3455
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3453
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3454
+            } elseif ( ! empty ($query_params['limit'])) {
3455
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3456 3456
             }
3457 3457
         }
3458 3458
         //set order by
@@ -3484,10 +3484,10 @@  discard block
 block discarded – undo
3484 3484
                 $order_array = array();
3485 3485
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3486 3486
                     $order = $this->_extract_order($order);
3487
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3487
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3488 3488
                 }
3489
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3490
-            } elseif (! empty ($query_params['order_by'])) {
3489
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3490
+            } elseif ( ! empty ($query_params['order_by'])) {
3491 3491
                 $this->_extract_related_model_info_from_query_param(
3492 3492
                     $query_params['order_by'],
3493 3493
                     $query_object,
@@ -3498,18 +3498,18 @@  discard block
 block discarded – undo
3498 3498
                     ? $this->_extract_order($query_params['order'])
3499 3499
                     : 'DESC';
3500 3500
                 $query_object->set_order_by_sql(
3501
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3501
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3502 3502
                 );
3503 3503
             }
3504 3504
         }
3505 3505
         //if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3506
-        if (! array_key_exists('order_by', $query_params)
3506
+        if ( ! array_key_exists('order_by', $query_params)
3507 3507
             && array_key_exists('order', $query_params)
3508 3508
             && ! empty($query_params['order'])
3509 3509
         ) {
3510 3510
             $pk_field = $this->get_primary_key_field();
3511 3511
             $order = $this->_extract_order($query_params['order']);
3512
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3512
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3513 3513
         }
3514 3514
         //set group by
3515 3515
         if (array_key_exists('group_by', $query_params)) {
@@ -3519,10 +3519,10 @@  discard block
 block discarded – undo
3519 3519
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3520 3520
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3521 3521
                 }
3522
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3523
-            } elseif (! empty ($query_params['group_by'])) {
3522
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3523
+            } elseif ( ! empty ($query_params['group_by'])) {
3524 3524
                 $query_object->set_group_by_sql(
3525
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3525
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3526 3526
                 );
3527 3527
             }
3528 3528
         }
@@ -3532,7 +3532,7 @@  discard block
 block discarded – undo
3532 3532
         }
3533 3533
         //now, just verify they didn't pass anything wack
3534 3534
         foreach ($query_params as $query_key => $query_value) {
3535
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3535
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3536 3536
                 throw new EE_Error(
3537 3537
                     sprintf(
3538 3538
                         __(
@@ -3631,22 +3631,22 @@  discard block
 block discarded – undo
3631 3631
         $where_query_params = array()
3632 3632
     ) {
3633 3633
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3634
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3634
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3635 3635
             throw new EE_Error(sprintf(__("You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3636 3636
                 "event_espresso"), $use_default_where_conditions,
3637 3637
                 implode(", ", $allowed_used_default_where_conditions_values)));
3638 3638
         }
3639 3639
         $universal_query_params = array();
3640
-        if ($this->_should_use_default_where_conditions( $use_default_where_conditions, true)) {
3640
+        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3641 3641
             $universal_query_params = $this->_get_default_where_conditions();
3642
-        } else if ($this->_should_use_minimum_where_conditions( $use_default_where_conditions, true)) {
3642
+        } else if ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3643 3643
             $universal_query_params = $this->_get_minimum_where_conditions();
3644 3644
         }
3645 3645
         foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3646 3646
             $related_model = $this->get_related_model_obj($model_name);
3647
-            if ( $this->_should_use_default_where_conditions( $use_default_where_conditions, false)) {
3647
+            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3648 3648
                 $related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3649
-            } elseif ($this->_should_use_minimum_where_conditions( $use_default_where_conditions, false)) {
3649
+            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3650 3650
                 $related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3651 3651
             } else {
3652 3652
                 //we don't want to add full or even minimum default where conditions from this model, so just continue
@@ -3679,7 +3679,7 @@  discard block
 block discarded – undo
3679 3679
      * @param bool $for_this_model false means this is for OTHER related models
3680 3680
      * @return bool
3681 3681
      */
3682
-    private function _should_use_default_where_conditions( $default_where_conditions_value, $for_this_model = true )
3682
+    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3683 3683
     {
3684 3684
         return (
3685 3685
                    $for_this_model
@@ -3758,7 +3758,7 @@  discard block
 block discarded – undo
3758 3758
     ) {
3759 3759
         $null_friendly_where_conditions = array();
3760 3760
         $none_overridden = true;
3761
-        $or_condition_key_for_defaults = 'OR*' . get_class($model);
3761
+        $or_condition_key_for_defaults = 'OR*'.get_class($model);
3762 3762
         foreach ($default_where_conditions as $key => $val) {
3763 3763
             if (isset($provided_where_conditions[$key])) {
3764 3764
                 $none_overridden = false;
@@ -3876,7 +3876,7 @@  discard block
 block discarded – undo
3876 3876
             foreach ($tables as $table_obj) {
3877 3877
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3878 3878
                                        . $table_obj->get_fully_qualified_pk_column();
3879
-                if (! in_array($qualified_pk_column, $selects)) {
3879
+                if ( ! in_array($qualified_pk_column, $selects)) {
3880 3880
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3881 3881
                 }
3882 3882
             }
@@ -4022,9 +4022,9 @@  discard block
 block discarded – undo
4022 4022
         $query_parameter_type
4023 4023
     ) {
4024 4024
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4025
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4025
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4026 4026
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4027
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4027
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4028 4028
                 if ($possible_join_string === '') {
4029 4029
                     //nothing left to $query_param
4030 4030
                     //we should actually end in a field name, not a model like this!
@@ -4151,7 +4151,7 @@  discard block
 block discarded – undo
4151 4151
     {
4152 4152
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4153 4153
         if ($SQL) {
4154
-            return " WHERE " . $SQL;
4154
+            return " WHERE ".$SQL;
4155 4155
         }
4156 4156
         return '';
4157 4157
     }
@@ -4170,7 +4170,7 @@  discard block
 block discarded – undo
4170 4170
     {
4171 4171
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4172 4172
         if ($SQL) {
4173
-            return " HAVING " . $SQL;
4173
+            return " HAVING ".$SQL;
4174 4174
         }
4175 4175
         return '';
4176 4176
     }
@@ -4189,7 +4189,7 @@  discard block
 block discarded – undo
4189 4189
     {
4190 4190
         $where_clauses = array();
4191 4191
         foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4192
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);//str_replace("*",'',$query_param);
4192
+            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param); //str_replace("*",'',$query_param);
4193 4193
             if (in_array($query_param, $this->_logic_query_param_keys)) {
4194 4194
                 switch ($query_param) {
4195 4195
                     case 'not':
@@ -4217,7 +4217,7 @@  discard block
 block discarded – undo
4217 4217
             } else {
4218 4218
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4219 4219
                 //if it's not a normal field, maybe it's a custom selection?
4220
-                if (! $field_obj) {
4220
+                if ( ! $field_obj) {
4221 4221
                     if ($this->_custom_selections instanceof CustomSelects) {
4222 4222
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4223 4223
                     } else {
@@ -4226,7 +4226,7 @@  discard block
 block discarded – undo
4226 4226
                     }
4227 4227
                 }
4228 4228
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4229
-                $where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4229
+                $where_clauses[] = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4230 4230
             }
4231 4231
         }
4232 4232
         return $where_clauses ? implode($glue, $where_clauses) : '';
@@ -4247,7 +4247,7 @@  discard block
 block discarded – undo
4247 4247
         if ($field) {
4248 4248
             $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param($field->get_model_name(),
4249 4249
                 $query_param);
4250
-            return $table_alias_prefix . $field->get_qualified_column();
4250
+            return $table_alias_prefix.$field->get_qualified_column();
4251 4251
         }
4252 4252
         if ($this->_custom_selections instanceof CustomSelects
4253 4253
             && in_array($query_param, $this->_custom_selections->columnAliases(), true)
@@ -4304,7 +4304,7 @@  discard block
 block discarded – undo
4304 4304
     {
4305 4305
         if (is_array($op_and_value)) {
4306 4306
             $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4307
-            if (! $operator) {
4307
+            if ( ! $operator) {
4308 4308
                 $php_array_like_string = array();
4309 4309
                 foreach ($op_and_value as $key => $value) {
4310 4310
                     $php_array_like_string[] = "$key=>$value";
@@ -4326,14 +4326,14 @@  discard block
 block discarded – undo
4326 4326
         }
4327 4327
         //check to see if the value is actually another field
4328 4328
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4329
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4329
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4330 4330
         }
4331 4331
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4332 4332
             //in this case, the value should be an array, or at least a comma-separated list
4333 4333
             //it will need to handle a little differently
4334 4334
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4335 4335
             //note: $cleaned_value has already been run through $wpdb->prepare()
4336
-            return $operator . SP . $cleaned_value;
4336
+            return $operator.SP.$cleaned_value;
4337 4337
         }
4338 4338
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4339 4339
             //the value should be an array with count of two.
@@ -4349,7 +4349,7 @@  discard block
 block discarded – undo
4349 4349
                 );
4350 4350
             }
4351 4351
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4352
-            return $operator . SP . $cleaned_value;
4352
+            return $operator.SP.$cleaned_value;
4353 4353
         }
4354 4354
         if (in_array($operator, $this->valid_null_style_operators())) {
4355 4355
             if ($value !== null) {
@@ -4369,10 +4369,10 @@  discard block
 block discarded – undo
4369 4369
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4370 4370
             //if the operator is 'LIKE', we want to allow percent signs (%) and not
4371 4371
             //remove other junk. So just treat it as a string.
4372
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4372
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4373 4373
         }
4374
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4375
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4374
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4375
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4376 4376
         }
4377 4377
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4378 4378
             throw new EE_Error(
@@ -4386,7 +4386,7 @@  discard block
 block discarded – undo
4386 4386
                 )
4387 4387
             );
4388 4388
         }
4389
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4389
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4390 4390
             throw new EE_Error(
4391 4391
                 sprintf(
4392 4392
                     __(
@@ -4426,7 +4426,7 @@  discard block
 block discarded – undo
4426 4426
         foreach ($values as $value) {
4427 4427
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4428 4428
         }
4429
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4429
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4430 4430
     }
4431 4431
 
4432 4432
 
@@ -4467,7 +4467,7 @@  discard block
 block discarded – undo
4467 4467
                                 . $main_table->get_table_name()
4468 4468
                                 . " WHERE FALSE";
4469 4469
         }
4470
-        return "(" . implode(",", $cleaned_values) . ")";
4470
+        return "(".implode(",", $cleaned_values).")";
4471 4471
     }
4472 4472
 
4473 4473
 
@@ -4486,7 +4486,7 @@  discard block
 block discarded – undo
4486 4486
             return $wpdb->prepare($field_obj->get_wpdb_data_type(),
4487 4487
                 $this->_prepare_value_for_use_in_db($value, $field_obj));
4488 4488
         } //$field_obj should really just be a data type
4489
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4489
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4490 4490
             throw new EE_Error(
4491 4491
                 sprintf(
4492 4492
                     __("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4614,10 +4614,10 @@  discard block
 block discarded – undo
4614 4614
      */
4615 4615
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4616 4616
     {
4617
-        $table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4617
+        $table_prefix = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain) ? '' : '__');
4618 4618
         $qualified_columns = array();
4619 4619
         foreach ($this->field_settings() as $field_name => $field) {
4620
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4620
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4621 4621
         }
4622 4622
         return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4623 4623
     }
@@ -4641,11 +4641,11 @@  discard block
 block discarded – undo
4641 4641
             if ($table_obj instanceof EE_Primary_Table) {
4642 4642
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4643 4643
                     ? $table_obj->get_select_join_limit($limit)
4644
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4644
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4645 4645
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4646 4646
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4647 4647
                     ? $table_obj->get_select_join_limit_join($limit)
4648
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4648
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4649 4649
             }
4650 4650
         }
4651 4651
         return $SQL;
@@ -4733,12 +4733,12 @@  discard block
 block discarded – undo
4733 4733
      */
4734 4734
     public function get_related_model_obj($model_name)
4735 4735
     {
4736
-        $model_classname = "EEM_" . $model_name;
4737
-        if (! class_exists($model_classname)) {
4736
+        $model_classname = "EEM_".$model_name;
4737
+        if ( ! class_exists($model_classname)) {
4738 4738
             throw new EE_Error(sprintf(__("You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4739 4739
                 'event_espresso'), $model_name, $model_classname));
4740 4740
         }
4741
-        return call_user_func($model_classname . "::instance");
4741
+        return call_user_func($model_classname."::instance");
4742 4742
     }
4743 4743
 
4744 4744
 
@@ -4785,7 +4785,7 @@  discard block
 block discarded – undo
4785 4785
     public function related_settings_for($relation_name)
4786 4786
     {
4787 4787
         $relatedModels = $this->relation_settings();
4788
-        if (! array_key_exists($relation_name, $relatedModels)) {
4788
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
4789 4789
             throw new EE_Error(
4790 4790
                 sprintf(
4791 4791
                     __('Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
@@ -4813,7 +4813,7 @@  discard block
 block discarded – undo
4813 4813
     public function field_settings_for($fieldName, $include_db_only_fields = true)
4814 4814
     {
4815 4815
         $fieldSettings = $this->field_settings($include_db_only_fields);
4816
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4816
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
4817 4817
             throw new EE_Error(sprintf(__("There is no field/column '%s' on '%s'", 'event_espresso'), $fieldName,
4818 4818
                 get_class($this)));
4819 4819
         }
@@ -4886,7 +4886,7 @@  discard block
 block discarded – undo
4886 4886
                     break;
4887 4887
                 }
4888 4888
             }
4889
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
4889
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
4890 4890
                 throw new EE_Error(sprintf(__("There is no Primary Key defined on model %s", 'event_espresso'),
4891 4891
                     get_class($this)));
4892 4892
             }
@@ -4945,7 +4945,7 @@  discard block
 block discarded – undo
4945 4945
      */
4946 4946
     public function get_foreign_key_to($model_name)
4947 4947
     {
4948
-        if (! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4948
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4949 4949
             foreach ($this->field_settings() as $field) {
4950 4950
                 if (
4951 4951
                     $field instanceof EE_Foreign_Key_Field_Base
@@ -4955,7 +4955,7 @@  discard block
 block discarded – undo
4955 4955
                     break;
4956 4956
                 }
4957 4957
             }
4958
-            if (! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4958
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4959 4959
                 throw new EE_Error(sprintf(__("There is no foreign key field pointing to model %s on model %s",
4960 4960
                     'event_espresso'), $model_name, get_class($this)));
4961 4961
             }
@@ -5006,7 +5006,7 @@  discard block
 block discarded – undo
5006 5006
             foreach ($this->_fields as $fields_corresponding_to_table) {
5007 5007
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5008 5008
                     /** @var $field_obj EE_Model_Field_Base */
5009
-                    if (! $field_obj->is_db_only_field()) {
5009
+                    if ( ! $field_obj->is_db_only_field()) {
5010 5010
                         $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5011 5011
                     }
5012 5012
                 }
@@ -5035,7 +5035,7 @@  discard block
 block discarded – undo
5035 5035
         $count_if_model_has_no_primary_key = 0;
5036 5036
         $has_primary_key = $this->has_primary_key_field();
5037 5037
         $primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5038
-        foreach ((array)$rows as $row) {
5038
+        foreach ((array) $rows as $row) {
5039 5039
             if (empty($row)) {
5040 5040
                 //wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5041 5041
                 return array();
@@ -5053,7 +5053,7 @@  discard block
 block discarded – undo
5053 5053
                 }
5054 5054
             }
5055 5055
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5056
-            if (! $classInstance) {
5056
+            if ( ! $classInstance) {
5057 5057
                 throw new EE_Error(
5058 5058
                     sprintf(
5059 5059
                         __('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5172,7 +5172,7 @@  discard block
 block discarded – undo
5172 5172
      */
5173 5173
     public function instantiate_class_from_array_or_object($cols_n_values)
5174 5174
     {
5175
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5175
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5176 5176
             $cols_n_values = get_object_vars($cols_n_values);
5177 5177
         }
5178 5178
         $primary_key = null;
@@ -5197,7 +5197,7 @@  discard block
 block discarded – undo
5197 5197
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5198 5198
         if ($primary_key) {
5199 5199
             $classInstance = $this->get_from_entity_map($primary_key);
5200
-            if (! $classInstance) {
5200
+            if ( ! $classInstance) {
5201 5201
                 $classInstance = EE_Registry::instance()
5202 5202
                                             ->load_class($className,
5203 5203
                                                 array($this_model_fields_n_values, $this->_timezone), true, false);
@@ -5246,12 +5246,12 @@  discard block
 block discarded – undo
5246 5246
     public function add_to_entity_map(EE_Base_Class $object)
5247 5247
     {
5248 5248
         $className = $this->_get_class_name();
5249
-        if (! $object instanceof $className) {
5249
+        if ( ! $object instanceof $className) {
5250 5250
             throw new EE_Error(sprintf(__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5251 5251
                 is_object($object) ? get_class($object) : $object, $className));
5252 5252
         }
5253 5253
         /** @var $object EE_Base_Class */
5254
-        if (! $object->ID()) {
5254
+        if ( ! $object->ID()) {
5255 5255
             throw new EE_Error(sprintf(__("You tried storing a model object with NO ID in the %s entity mapper.",
5256 5256
                 "event_espresso"), get_class($this)));
5257 5257
         }
@@ -5320,7 +5320,7 @@  discard block
 block discarded – undo
5320 5320
             //there is a primary key on this table and its not set. Use defaults for all its columns
5321 5321
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5322 5322
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5323
-                    if (! $field_obj->is_db_only_field()) {
5323
+                    if ( ! $field_obj->is_db_only_field()) {
5324 5324
                         //prepare field as if its coming from db
5325 5325
                         $prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5326 5326
                         $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
@@ -5329,7 +5329,7 @@  discard block
 block discarded – undo
5329 5329
             } else {
5330 5330
                 //the table's rows existed. Use their values
5331 5331
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5332
-                    if (! $field_obj->is_db_only_field()) {
5332
+                    if ( ! $field_obj->is_db_only_field()) {
5333 5333
                         $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5334 5334
                             $cols_n_values, $field_obj->get_qualified_column(),
5335 5335
                             $field_obj->get_table_column()
@@ -5444,7 +5444,7 @@  discard block
 block discarded – undo
5444 5444
      */
5445 5445
     private function _get_class_name()
5446 5446
     {
5447
-        return "EE_" . $this->get_this_model_name();
5447
+        return "EE_".$this->get_this_model_name();
5448 5448
     }
5449 5449
 
5450 5450
 
@@ -5459,7 +5459,7 @@  discard block
 block discarded – undo
5459 5459
      */
5460 5460
     public function item_name($quantity = 1)
5461 5461
     {
5462
-        return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5462
+        return (int) $quantity === 1 ? $this->singular_item : $this->plural_item;
5463 5463
     }
5464 5464
 
5465 5465
 
@@ -5492,7 +5492,7 @@  discard block
 block discarded – undo
5492 5492
     {
5493 5493
         $className = get_class($this);
5494 5494
         $tagName = "FHEE__{$className}__{$methodName}";
5495
-        if (! has_filter($tagName)) {
5495
+        if ( ! has_filter($tagName)) {
5496 5496
             throw new EE_Error(
5497 5497
                 sprintf(
5498 5498
                     __('Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
@@ -5718,7 +5718,7 @@  discard block
 block discarded – undo
5718 5718
         $key_vals_in_combined_pk = array();
5719 5719
         parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5720 5720
         foreach ($key_fields as $key_field_name => $field_obj) {
5721
-            if (! isset($key_vals_in_combined_pk[$key_field_name])) {
5721
+            if ( ! isset($key_vals_in_combined_pk[$key_field_name])) {
5722 5722
                 return null;
5723 5723
             }
5724 5724
         }
@@ -5739,7 +5739,7 @@  discard block
 block discarded – undo
5739 5739
     {
5740 5740
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5741 5741
         foreach ($keys_it_should_have as $key) {
5742
-            if (! isset($key_vals[$key])) {
5742
+            if ( ! isset($key_vals[$key])) {
5743 5743
                 return false;
5744 5744
             }
5745 5745
         }
@@ -5793,7 +5793,7 @@  discard block
 block discarded – undo
5793 5793
      */
5794 5794
     public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5795 5795
     {
5796
-        if (! is_array($query_params)) {
5796
+        if ( ! is_array($query_params)) {
5797 5797
             EE_Error::doing_it_wrong('EEM_Base::get_one_copy',
5798 5798
                 sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5799 5799
                     gettype($query_params)), '4.6.0');
@@ -5859,7 +5859,7 @@  discard block
 block discarded – undo
5859 5859
      * Gets the valid operators
5860 5860
      * @return array keys are accepted strings, values are the SQL they are converted to
5861 5861
      */
5862
-    public function valid_operators(){
5862
+    public function valid_operators() {
5863 5863
         return $this->_valid_operators;
5864 5864
     }
5865 5865
 
@@ -5947,7 +5947,7 @@  discard block
 block discarded – undo
5947 5947
      */
5948 5948
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
5949 5949
     {
5950
-        if (! $this->has_primary_key_field()) {
5950
+        if ( ! $this->has_primary_key_field()) {
5951 5951
             if (WP_DEBUG) {
5952 5952
                 EE_Error::add_error(
5953 5953
                     __('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -5960,7 +5960,7 @@  discard block
 block discarded – undo
5960 5960
         $IDs = array();
5961 5961
         foreach ($model_objects as $model_object) {
5962 5962
             $id = $model_object->ID();
5963
-            if (! $id) {
5963
+            if ( ! $id) {
5964 5964
                 if ($filter_out_empty_ids) {
5965 5965
                     continue;
5966 5966
                 }
@@ -6056,8 +6056,8 @@  discard block
 block discarded – undo
6056 6056
         $missing_caps = array();
6057 6057
         $cap_restrictions = $this->cap_restrictions($context);
6058 6058
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6059
-            if (! EE_Capabilities::instance()
6060
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6059
+            if ( ! EE_Capabilities::instance()
6060
+                                 ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6061 6061
             ) {
6062 6062
                 $missing_caps[$cap] = $restriction_if_no_cap;
6063 6063
             }
@@ -6209,7 +6209,7 @@  discard block
 block discarded – undo
6209 6209
     {
6210 6210
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6211 6211
             if ($query_param_key === $logic_query_param_key
6212
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6212
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6213 6213
             ) {
6214 6214
                 return true;
6215 6215
             }
Please login to merge, or discard this patch.
Indentation   +6177 added lines, -6177 removed lines patch added patch discarded remove patch
@@ -34,6185 +34,6185 @@
 block discarded – undo
34 34
 abstract class EEM_Base extends EE_Base implements ResettableInterface
35 35
 {
36 36
 
37
-    /**
38
-     * Flag to indicate whether the values provided to EEM_Base have already been prepared
39
-     * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
40
-     * They almost always WILL NOT, but it's not necessarily a requirement.
41
-     * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
42
-     *
43
-     * @var boolean
44
-     */
45
-    private $_values_already_prepared_by_model_object = 0;
46
-
47
-    /**
48
-     * when $_values_already_prepared_by_model_object equals this, we assume
49
-     * the data is just like form input that needs to have the model fields'
50
-     * prepare_for_set and prepare_for_use_in_db called on it
51
-     */
52
-    const not_prepared_by_model_object = 0;
53
-
54
-    /**
55
-     * when $_values_already_prepared_by_model_object equals this, we
56
-     * assume this value is coming from a model object and doesn't need to have
57
-     * prepare_for_set called on it, just prepare_for_use_in_db is used
58
-     */
59
-    const prepared_by_model_object = 1;
60
-
61
-    /**
62
-     * when $_values_already_prepared_by_model_object equals this, we assume
63
-     * the values are already to be used in the database (ie no processing is done
64
-     * on them by the model's fields)
65
-     */
66
-    const prepared_for_use_in_db = 2;
67
-
68
-
69
-    protected $singular_item = 'Item';
70
-
71
-    protected $plural_item   = 'Items';
72
-
73
-    /**
74
-     * @type \EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
75
-     */
76
-    protected $_tables;
77
-
78
-    /**
79
-     * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
80
-     * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
81
-     * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
82
-     *
83
-     * @var \EE_Model_Field_Base[][] $_fields
84
-     */
85
-    protected $_fields;
86
-
87
-    /**
88
-     * array of different kinds of relations
89
-     *
90
-     * @var \EE_Model_Relation_Base[] $_model_relations
91
-     */
92
-    protected $_model_relations;
93
-
94
-    /**
95
-     * @var \EE_Index[] $_indexes
96
-     */
97
-    protected $_indexes = array();
98
-
99
-    /**
100
-     * Default strategy for getting where conditions on this model. This strategy is used to get default
101
-     * where conditions which are added to get_all, update, and delete queries. They can be overridden
102
-     * by setting the same columns as used in these queries in the query yourself.
103
-     *
104
-     * @var EE_Default_Where_Conditions
105
-     */
106
-    protected $_default_where_conditions_strategy;
107
-
108
-    /**
109
-     * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
110
-     * This is particularly useful when you want something between 'none' and 'default'
111
-     *
112
-     * @var EE_Default_Where_Conditions
113
-     */
114
-    protected $_minimum_where_conditions_strategy;
115
-
116
-    /**
117
-     * String describing how to find the "owner" of this model's objects.
118
-     * When there is a foreign key on this model to the wp_users table, this isn't needed.
119
-     * But when there isn't, this indicates which related model, or transiently-related model,
120
-     * has the foreign key to the wp_users table.
121
-     * Eg, for EEM_Registration this would be 'Event' because registrations are directly
122
-     * related to events, and events have a foreign key to wp_users.
123
-     * On EEM_Transaction, this would be 'Transaction.Event'
124
-     *
125
-     * @var string
126
-     */
127
-    protected $_model_chain_to_wp_user = '';
128
-
129
-    /**
130
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
131
-     * don't need it (particularly CPT models)
132
-     *
133
-     * @var bool
134
-     */
135
-    protected $_ignore_where_strategy = false;
136
-
137
-    /**
138
-     * String used in caps relating to this model. Eg, if the caps relating to this
139
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
140
-     *
141
-     * @var string. If null it hasn't been initialized yet. If false then we
142
-     * have indicated capabilities don't apply to this
143
-     */
144
-    protected $_caps_slug = null;
145
-
146
-    /**
147
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
148
-     * and next-level keys are capability names, and each's value is a
149
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
150
-     * they specify which context to use (ie, frontend, backend, edit or delete)
151
-     * and then each capability in the corresponding sub-array that they're missing
152
-     * adds the where conditions onto the query.
153
-     *
154
-     * @var array
155
-     */
156
-    protected $_cap_restrictions = array(
157
-        self::caps_read       => array(),
158
-        self::caps_read_admin => array(),
159
-        self::caps_edit       => array(),
160
-        self::caps_delete     => array(),
161
-    );
162
-
163
-    /**
164
-     * Array defining which cap restriction generators to use to create default
165
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
166
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
167
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
168
-     * automatically set this to false (not just null).
169
-     *
170
-     * @var EE_Restriction_Generator_Base[]
171
-     */
172
-    protected $_cap_restriction_generators = array();
173
-
174
-    /**
175
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
176
-     */
177
-    const caps_read       = 'read';
178
-
179
-    const caps_read_admin = 'read_admin';
180
-
181
-    const caps_edit       = 'edit';
182
-
183
-    const caps_delete     = 'delete';
184
-
185
-    /**
186
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
187
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
188
-     * maps to 'read' because when looking for relevant permissions we're going to use
189
-     * 'read' in teh capabilities names like 'ee_read_events' etc.
190
-     *
191
-     * @var array
192
-     */
193
-    protected $_cap_contexts_to_cap_action_map = array(
194
-        self::caps_read       => 'read',
195
-        self::caps_read_admin => 'read',
196
-        self::caps_edit       => 'edit',
197
-        self::caps_delete     => 'delete',
198
-    );
199
-
200
-    /**
201
-     * Timezone
202
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
203
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
204
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
205
-     * EE_Datetime_Field data type will have access to it.
206
-     *
207
-     * @var string
208
-     */
209
-    protected $_timezone;
210
-
211
-
212
-    /**
213
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
214
-     * multisite.
215
-     *
216
-     * @var int
217
-     */
218
-    protected static $_model_query_blog_id;
219
-
220
-    /**
221
-     * A copy of _fields, except the array keys are the model names pointed to by
222
-     * the field
223
-     *
224
-     * @var EE_Model_Field_Base[]
225
-     */
226
-    private $_cache_foreign_key_to_fields = array();
227
-
228
-    /**
229
-     * Cached list of all the fields on the model, indexed by their name
230
-     *
231
-     * @var EE_Model_Field_Base[]
232
-     */
233
-    private $_cached_fields = null;
234
-
235
-    /**
236
-     * Cached list of all the fields on the model, except those that are
237
-     * marked as only pertinent to the database
238
-     *
239
-     * @var EE_Model_Field_Base[]
240
-     */
241
-    private $_cached_fields_non_db_only = null;
242
-
243
-    /**
244
-     * A cached reference to the primary key for quick lookup
245
-     *
246
-     * @var EE_Model_Field_Base
247
-     */
248
-    private $_primary_key_field = null;
249
-
250
-    /**
251
-     * Flag indicating whether this model has a primary key or not
252
-     *
253
-     * @var boolean
254
-     */
255
-    protected $_has_primary_key_field = null;
256
-
257
-    /**
258
-     * Whether or not this model is based off a table in WP core only (CPTs should set
259
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
260
-     * This should be true for models that deal with data that should exist independent of EE.
261
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
262
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
263
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
264
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
265
-     * @var boolean
266
-     */
267
-    protected $_wp_core_model = false;
268
-
269
-    /**
270
-     *    List of valid operators that can be used for querying.
271
-     * The keys are all operators we'll accept, the values are the real SQL
272
-     * operators used
273
-     *
274
-     * @var array
275
-     */
276
-    protected $_valid_operators = array(
277
-        '='           => '=',
278
-        '<='          => '<=',
279
-        '<'           => '<',
280
-        '>='          => '>=',
281
-        '>'           => '>',
282
-        '!='          => '!=',
283
-        'LIKE'        => 'LIKE',
284
-        'like'        => 'LIKE',
285
-        'NOT_LIKE'    => 'NOT LIKE',
286
-        'not_like'    => 'NOT LIKE',
287
-        'NOT LIKE'    => 'NOT LIKE',
288
-        'not like'    => 'NOT LIKE',
289
-        'IN'          => 'IN',
290
-        'in'          => 'IN',
291
-        'NOT_IN'      => 'NOT IN',
292
-        'not_in'      => 'NOT IN',
293
-        'NOT IN'      => 'NOT IN',
294
-        'not in'      => 'NOT IN',
295
-        'between'     => 'BETWEEN',
296
-        'BETWEEN'     => 'BETWEEN',
297
-        'IS_NOT_NULL' => 'IS NOT NULL',
298
-        'is_not_null' => 'IS NOT NULL',
299
-        'IS NOT NULL' => 'IS NOT NULL',
300
-        'is not null' => 'IS NOT NULL',
301
-        'IS_NULL'     => 'IS NULL',
302
-        'is_null'     => 'IS NULL',
303
-        'IS NULL'     => 'IS NULL',
304
-        'is null'     => 'IS NULL',
305
-        'REGEXP'      => 'REGEXP',
306
-        'regexp'      => 'REGEXP',
307
-        'NOT_REGEXP'  => 'NOT REGEXP',
308
-        'not_regexp'  => 'NOT REGEXP',
309
-        'NOT REGEXP'  => 'NOT REGEXP',
310
-        'not regexp'  => 'NOT REGEXP',
311
-    );
312
-
313
-    /**
314
-     * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
315
-     *
316
-     * @var array
317
-     */
318
-    protected $_in_style_operators = array('IN', 'NOT IN');
319
-
320
-    /**
321
-     * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
322
-     * '12-31-2012'"
323
-     *
324
-     * @var array
325
-     */
326
-    protected $_between_style_operators = array('BETWEEN');
327
-
328
-    /**
329
-     * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
330
-     * @var array
331
-     */
332
-    protected $_like_style_operators = array('LIKE', 'NOT LIKE');
333
-    /**
334
-     * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
335
-     * on a join table.
336
-     *
337
-     * @var array
338
-     */
339
-    protected $_null_style_operators = array('IS NOT NULL', 'IS NULL');
340
-
341
-    /**
342
-     * Allowed values for $query_params['order'] for ordering in queries
343
-     *
344
-     * @var array
345
-     */
346
-    protected $_allowed_order_values = array('asc', 'desc', 'ASC', 'DESC');
347
-
348
-    /**
349
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
350
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
351
-     *
352
-     * @var array
353
-     */
354
-    private $_logic_query_param_keys = array('not', 'and', 'or', 'NOT', 'AND', 'OR');
355
-
356
-    /**
357
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
358
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
359
-     *
360
-     * @var array
361
-     */
362
-    private $_allowed_query_params = array(
363
-        0,
364
-        'limit',
365
-        'order_by',
366
-        'group_by',
367
-        'having',
368
-        'force_join',
369
-        'order',
370
-        'on_join_limit',
371
-        'default_where_conditions',
372
-        'caps',
373
-        'extra_selects'
374
-    );
375
-
376
-    /**
377
-     * All the data types that can be used in $wpdb->prepare statements.
378
-     *
379
-     * @var array
380
-     */
381
-    private $_valid_wpdb_data_types = array('%d', '%s', '%f');
382
-
383
-    /**
384
-     * @var EE_Registry $EE
385
-     */
386
-    protected $EE = null;
387
-
388
-
389
-    /**
390
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
391
-     *
392
-     * @var int
393
-     */
394
-    protected $_show_next_x_db_queries = 0;
395
-
396
-    /**
397
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
398
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
399
-     * WHERE, GROUP_BY, etc.
400
-     *
401
-     * @var CustomSelects
402
-     */
403
-    protected $_custom_selections = array();
404
-
405
-    /**
406
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
407
-     * caches every model object we've fetched from the DB on this request
408
-     *
409
-     * @var array
410
-     */
411
-    protected $_entity_map;
412
-
413
-    /**
414
-     * @var LoaderInterface $loader
415
-     */
416
-    private static $loader;
417
-
418
-
419
-    /**
420
-     * constant used to show EEM_Base has not yet verified the db on this http request
421
-     */
422
-    const db_verified_none = 0;
423
-
424
-    /**
425
-     * constant used to show EEM_Base has verified the EE core db on this http request,
426
-     * but not the addons' dbs
427
-     */
428
-    const db_verified_core = 1;
429
-
430
-    /**
431
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
432
-     * the EE core db too)
433
-     */
434
-    const db_verified_addons = 2;
435
-
436
-    /**
437
-     * indicates whether an EEM_Base child has already re-verified the DB
438
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
439
-     * looking like EEM_Base::db_verified_*
440
-     *
441
-     * @var int - 0 = none, 1 = core, 2 = addons
442
-     */
443
-    protected static $_db_verification_level = EEM_Base::db_verified_none;
444
-
445
-    /**
446
-     * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
447
-     *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
448
-     *        registrations for non-trashed tickets for non-trashed datetimes)
449
-     */
450
-    const default_where_conditions_all = 'all';
451
-
452
-    /**
453
-     * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
454
-     *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
455
-     *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
456
-     *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
457
-     *        models which share tables with other models, this can return data for the wrong model.
458
-     */
459
-    const default_where_conditions_this_only = 'this_model_only';
460
-
461
-    /**
462
-     * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
463
-     *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
464
-     *        return all registrations related to non-trashed tickets and non-trashed datetimes)
465
-     */
466
-    const default_where_conditions_others_only = 'other_models_only';
467
-
468
-    /**
469
-     * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
470
-     *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
471
-     *        their table with other models, like the Event and Venue models. For example, when querying for events
472
-     *        ordered by their venues' name, this will be sure to only return real events with associated real venues
473
-     *        (regardless of whether those events and venues are trashed)
474
-     *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
475
-     *        events.
476
-     */
477
-    const default_where_conditions_minimum_all = 'minimum';
478
-
479
-    /**
480
-     * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
481
-     *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
482
-     *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
483
-     *        not)
484
-     */
485
-    const default_where_conditions_minimum_others = 'full_this_minimum_others';
486
-
487
-    /**
488
-     * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
489
-     *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
490
-     *        it's possible it will return table entries for other models. You should use
491
-     *        EEM_Base::default_where_conditions_minimum_all instead.
492
-     */
493
-    const default_where_conditions_none = 'none';
494
-
495
-
496
-
497
-    /**
498
-     * About all child constructors:
499
-     * they should define the _tables, _fields and _model_relations arrays.
500
-     * Should ALWAYS be called after child constructor.
501
-     * In order to make the child constructors to be as simple as possible, this parent constructor
502
-     * finalizes constructing all the object's attributes.
503
-     * Generally, rather than requiring a child to code
504
-     * $this->_tables = array(
505
-     *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
506
-     *        ...);
507
-     *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
508
-     * each EE_Table has a function to set the table's alias after the constructor, using
509
-     * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
510
-     * do something similar.
511
-     *
512
-     * @param null $timezone
513
-     * @throws EE_Error
514
-     */
515
-    protected function __construct($timezone = null)
516
-    {
517
-        // check that the model has not been loaded too soon
518
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
519
-            throw new EE_Error (
520
-                sprintf(
521
-                    __('The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
522
-                        'event_espresso'),
523
-                    get_class($this)
524
-                )
525
-            );
526
-        }
527
-        /**
528
-         * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
529
-         */
530
-        if (empty(EEM_Base::$_model_query_blog_id)) {
531
-            EEM_Base::set_model_query_blog_id();
532
-        }
533
-        /**
534
-         * Filters the list of tables on a model. It is best to NOT use this directly and instead
535
-         * just use EE_Register_Model_Extension
536
-         *
537
-         * @var EE_Table_Base[] $_tables
538
-         */
539
-        $this->_tables = (array)apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
540
-        foreach ($this->_tables as $table_alias => $table_obj) {
541
-            /** @var $table_obj EE_Table_Base */
542
-            $table_obj->_construct_finalize_with_alias($table_alias);
543
-            if ($table_obj instanceof EE_Secondary_Table) {
544
-                /** @var $table_obj EE_Secondary_Table */
545
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
546
-            }
547
-        }
548
-        /**
549
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
550
-         * EE_Register_Model_Extension
551
-         *
552
-         * @param EE_Model_Field_Base[] $_fields
553
-         */
554
-        $this->_fields = (array)apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
555
-        $this->_invalidate_field_caches();
556
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
557
-            if (! array_key_exists($table_alias, $this->_tables)) {
558
-                throw new EE_Error(sprintf(__("Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
559
-                    'event_espresso'), $table_alias, implode(",", $this->_fields)));
560
-            }
561
-            foreach ($fields_for_table as $field_name => $field_obj) {
562
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
563
-                //primary key field base has a slightly different _construct_finalize
564
-                /** @var $field_obj EE_Model_Field_Base */
565
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
566
-            }
567
-        }
568
-        // everything is related to Extra_Meta
569
-        if (get_class($this) !== 'EEM_Extra_Meta') {
570
-            //make extra meta related to everything, but don't block deleting things just
571
-            //because they have related extra meta info. For now just orphan those extra meta
572
-            //in the future we should automatically delete them
573
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
574
-        }
575
-        //and change logs
576
-        if (get_class($this) !== 'EEM_Change_Log') {
577
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
578
-        }
579
-        /**
580
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
581
-         * EE_Register_Model_Extension
582
-         *
583
-         * @param EE_Model_Relation_Base[] $_model_relations
584
-         */
585
-        $this->_model_relations = (array)apply_filters('FHEE__' . get_class($this) . '__construct__model_relations',
586
-            $this->_model_relations);
587
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
588
-            /** @var $relation_obj EE_Model_Relation_Base */
589
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
590
-        }
591
-        foreach ($this->_indexes as $index_name => $index_obj) {
592
-            /** @var $index_obj EE_Index */
593
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
594
-        }
595
-        $this->set_timezone($timezone);
596
-        //finalize default where condition strategy, or set default
597
-        if (! $this->_default_where_conditions_strategy) {
598
-            //nothing was set during child constructor, so set default
599
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
600
-        }
601
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
602
-        if (! $this->_minimum_where_conditions_strategy) {
603
-            //nothing was set during child constructor, so set default
604
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
605
-        }
606
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
607
-        //if the cap slug hasn't been set, and we haven't set it to false on purpose
608
-        //to indicate to NOT set it, set it to the logical default
609
-        if ($this->_caps_slug === null) {
610
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
611
-        }
612
-        //initialize the standard cap restriction generators if none were specified by the child constructor
613
-        if ($this->_cap_restriction_generators !== false) {
614
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
615
-                if (! isset($this->_cap_restriction_generators[$cap_context])) {
616
-                    $this->_cap_restriction_generators[$cap_context] = apply_filters(
617
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
618
-                        new EE_Restriction_Generator_Protected(),
619
-                        $cap_context,
620
-                        $this
621
-                    );
622
-                }
623
-            }
624
-        }
625
-        //if there are cap restriction generators, use them to make the default cap restrictions
626
-        if ($this->_cap_restriction_generators !== false) {
627
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
628
-                if (! $generator_object) {
629
-                    continue;
630
-                }
631
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
632
-                    throw new EE_Error(
633
-                        sprintf(
634
-                            __('Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
635
-                                'event_espresso'),
636
-                            $context,
637
-                            $this->get_this_model_name()
638
-                        )
639
-                    );
640
-                }
641
-                $action = $this->cap_action_for_context($context);
642
-                if (! $generator_object->construction_finalized()) {
643
-                    $generator_object->_construct_finalize($this, $action);
644
-                }
645
-            }
646
-        }
647
-        do_action('AHEE__' . get_class($this) . '__construct__end');
648
-    }
649
-
650
-
651
-
652
-    /**
653
-     * Used to set the $_model_query_blog_id static property.
654
-     *
655
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
656
-     *                      value for get_current_blog_id() will be used.
657
-     */
658
-    public static function set_model_query_blog_id($blog_id = 0)
659
-    {
660
-        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int)$blog_id : get_current_blog_id();
661
-    }
662
-
663
-
664
-
665
-    /**
666
-     * Returns whatever is set as the internal $model_query_blog_id.
667
-     *
668
-     * @return int
669
-     */
670
-    public static function get_model_query_blog_id()
671
-    {
672
-        return EEM_Base::$_model_query_blog_id;
673
-    }
674
-
675
-
676
-
677
-    /**
678
-     * This function is a singleton method used to instantiate the Espresso_model object
679
-     *
680
-     * @param string $timezone string representing the timezone we want to set for returned Date Time Strings
681
-     *                                (and any incoming timezone data that gets saved).
682
-     *                                Note this just sends the timezone info to the date time model field objects.
683
-     *                                Default is NULL
684
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
685
-     * @return static (as in the concrete child class)
686
-     * @throws EE_Error
687
-     * @throws InvalidArgumentException
688
-     * @throws InvalidDataTypeException
689
-     * @throws InvalidInterfaceException
690
-     */
691
-    public static function instance($timezone = null)
692
-    {
693
-        // check if instance of Espresso_model already exists
694
-        if (! static::$_instance instanceof static) {
695
-            // instantiate Espresso_model
696
-            static::$_instance = new static(
697
-                $timezone,
698
-                LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
699
-            );
700
-        }
701
-        //we might have a timezone set, let set_timezone decide what to do with it
702
-        static::$_instance->set_timezone($timezone);
703
-        // Espresso_model object
704
-        return static::$_instance;
705
-    }
706
-
707
-
708
-
709
-    /**
710
-     * resets the model and returns it
711
-     *
712
-     * @param null | string $timezone
713
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
714
-     * all its properties reset; if it wasn't instantiated, returns null)
715
-     * @throws EE_Error
716
-     * @throws ReflectionException
717
-     * @throws InvalidArgumentException
718
-     * @throws InvalidDataTypeException
719
-     * @throws InvalidInterfaceException
720
-     */
721
-    public static function reset($timezone = null)
722
-    {
723
-        if (static::$_instance instanceof EEM_Base) {
724
-            //let's try to NOT swap out the current instance for a new one
725
-            //because if someone has a reference to it, we can't remove their reference
726
-            //so it's best to keep using the same reference, but change the original object
727
-            //reset all its properties to their original values as defined in the class
728
-            $r = new ReflectionClass(get_class(static::$_instance));
729
-            $static_properties = $r->getStaticProperties();
730
-            foreach ($r->getDefaultProperties() as $property => $value) {
731
-                //don't set instance to null like it was originally,
732
-                //but it's static anyways, and we're ignoring static properties (for now at least)
733
-                if (! isset($static_properties[$property])) {
734
-                    static::$_instance->{$property} = $value;
735
-                }
736
-            }
737
-            //and then directly call its constructor again, like we would if we were creating a new one
738
-            static::$_instance->__construct(
739
-                $timezone,
740
-                LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
741
-            );
742
-            return self::instance();
743
-        }
744
-        return null;
745
-    }
746
-
747
-
748
-
749
-    /**
750
-     * @return LoaderInterface
751
-     * @throws InvalidArgumentException
752
-     * @throws InvalidDataTypeException
753
-     * @throws InvalidInterfaceException
754
-     */
755
-    private static function getLoader()
756
-    {
757
-        if(! EEM_Base::$loader instanceof LoaderInterface) {
758
-            EEM_Base::$loader = LoaderFactory::getLoader();
759
-        }
760
-        return EEM_Base::$loader;
761
-    }
762
-
763
-
764
-
765
-    /**
766
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
767
-     *
768
-     * @param  boolean $translated return localized strings or JUST the array.
769
-     * @return array
770
-     * @throws EE_Error
771
-     * @throws InvalidArgumentException
772
-     * @throws InvalidDataTypeException
773
-     * @throws InvalidInterfaceException
774
-     */
775
-    public function status_array($translated = false)
776
-    {
777
-        if (! array_key_exists('Status', $this->_model_relations)) {
778
-            return array();
779
-        }
780
-        $model_name = $this->get_this_model_name();
781
-        $status_type = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
782
-        $stati = EEM_Status::instance()->get_all(array(array('STS_type' => $status_type)));
783
-        $status_array = array();
784
-        foreach ($stati as $status) {
785
-            $status_array[$status->ID()] = $status->get('STS_code');
786
-        }
787
-        return $translated
788
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
789
-            : $status_array;
790
-    }
791
-
792
-
793
-
794
-    /**
795
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
796
-     *
797
-     * @param array $query_params             {
798
-     * @var array $0 (where) array {
799
-     *                                        eg: array('QST_display_text'=>'Are you bob?','QST_admin_text'=>'Determine
800
-     *                                        if user is bob') becomes SQL >> "...WHERE QST_display_text = 'Are you
801
-     *                                        bob?' AND QST_admin_text = 'Determine if user is bob'...") To add WHERE
802
-     *                                        conditions based on related models (and even
803
-     *                                        models-related-to-related-models) prepend the model's name onto the field
804
-     *                                        name. Eg,
805
-     *                                        EEM_Event::instance()->get_all(array(array('Venue.VNU_ID'=>12))); becomes
806
-     *                                        SQL >> "SELECT * FROM wp_posts AS Event_CPT LEFT JOIN wp_esp_event_meta
807
-     *                                        AS Event_Meta ON Event_CPT.ID = Event_Meta.EVT_ID LEFT JOIN
808
-     *                                        wp_esp_event_venue AS Event_Venue ON Event_Venue.EVT_ID=Event_CPT.ID LEFT
809
-     *                                        JOIN wp_posts AS Venue_CPT ON Venue_CPT.ID=Event_Venue.VNU_ID LEFT JOIN
810
-     *                                        wp_esp_venue_meta AS Venue_Meta ON Venue_CPT.ID = Venue_Meta.VNU_ID WHERE
811
-     *                                        Venue_CPT.ID = 12 Notice that automatically took care of joining Events
812
-     *                                        to Venues (even when each of those models actually consisted of two
813
-     *                                        tables). Also, you may chain the model relations together. Eg instead of
814
-     *                                        just having
815
-     *                                        "Venue.VNU_ID", you could have
816
-     *                                        "Registration.Attendee.ATT_ID" as a field on a query for events (because
817
-     *                                        events are related to Registrations, which are related to Attendees). You
818
-     *                                        can take it even further with
819
-     *                                        "Registration.Transaction.Payment.PAY_amount" etc. To change the operator
820
-     *                                        (from the default of '='), change the value to an numerically-indexed
821
-     *                                        array, where the first item in the list is the operator. eg: array(
822
-     *                                        'QST_display_text' => array('LIKE','%bob%'), 'QST_ID' => array('<',34),
823
-     *                                        'QST_wp_user' => array('in',array(1,2,7,23))) becomes SQL >> "...WHERE
824
-     *                                        QST_display_text LIKE '%bob%' AND QST_ID < 34 AND QST_wp_user IN
825
-     *                                        (1,2,7,23)...". Valid operators so far: =, !=, <, <=, >, >=, LIKE, NOT
826
-     *                                        LIKE, IN (followed by numeric-indexed array), NOT IN (dido), BETWEEN
827
-     *                                        (followed by an array with exactly 2 date strings), IS NULL, and IS NOT
828
-     *                                        NULL Values can be a string, int, or float. They can also be arrays IFF
829
-     *                                        the operator is IN. Also, values can actually be field names. To indicate
830
-     *                                        the value is a field, simply provide a third array item (true) to the
831
-     *                                        operator-value array like so: eg: array( 'DTT_reg_limit' => array('>',
832
-     *                                        'DTT_sold', TRUE) ) becomes SQL >> "...WHERE DTT_reg_limit > DTT_sold"
833
-     *                                        Note: you can also use related model field names like you would any other
834
-     *                                        field name. eg:
835
-     *                                        array('Datetime.DTT_reg_limit'=>array('=','Datetime.DTT_sold',TRUE) could
836
-     *                                        be used if you were querying EEM_Tickets (because Datetime is directly related to tickets) Also, by default all the where conditions are AND'd together. To override this, add an array key 'OR' (or 'AND') and the array to be OR'd together eg: array('OR'=>array('TXN_ID' => 23 , 'TXN_timestamp__>' =>
837
-     *                                        345678912)) becomes SQL >> "...WHERE TXN_ID = 23 OR TXN_timestamp =
838
-     *                                        345678912...". Also, to negate an entire set of conditions, use 'NOT' as
839
-     *                                        an array key. eg: array('NOT'=>array('TXN_total' =>
840
-     *                                        50, 'TXN_paid'=>23) becomes SQL >> "...where ! (TXN_total =50 AND
841
-     *                                        TXN_paid =23) Note: the 'glue' used to join each condition will continue
842
-     *                                        to be what you last specified. IE, "AND"s by default, but if you had
843
-     *                                        previously specified to use ORs to join, ORs will continue to be used.
844
-     *                                        So, if you specify to use an "OR" to join conditions, it will continue to
845
-     *                                        "stick" until you specify an AND. eg
846
-     *                                        array('OR'=>array('NOT'=>array('TXN_total' => 50,
847
-     *                                        'TXN_paid'=>23)),AND=>array('TXN_ID'=>1,'STS_ID'=>'TIN') becomes SQL >>
848
-     *                                        "...where ! (TXN_total =50 OR TXN_paid =23) AND TXN_ID=1 AND
849
-     *                                        STS_ID='TIN'" They can be nested indefinitely. eg:
850
-     *                                        array('OR'=>array('TXN_total' => 23, 'NOT'=> array( 'TXN_timestamp'=> 345678912, 'AND'=>array('TXN_paid' => 53, 'STS_ID' => 'TIN')))) becomes SQL >> "...WHERE TXN_total = 23 OR ! (TXN_timestamp = 345678912 OR (TXN_paid = 53 AND STS_ID = 'TIN'))..." GOTCHA: because this is an array, array keys must be unique, making it impossible to place two or more where conditions applying to the same field. eg: array('PAY_timestamp'=>array('>',$start_date),'PAY_timestamp'=>array('<',$end_date),'PAY_timestamp'=>array('!=',$special_date)), as PHP enforces that the array keys must be unique, thus removing the first two array entries with key 'PAY_timestamp'. becomes SQL >> "PAY_timestamp !=  4234232", ignoring the first two PAY_timestamp conditions). To overcome this, you can add a '*' character to the end of the field's name, followed by anything. These will be removed when generating the SQL string, but allow for the array keys to be unique. eg: you could rewrite the previous query as: array('PAY_timestamp'=>array('>',$start_date),'PAY_timestamp*1st'=>array('<',$end_date),'PAY_timestamp*2nd'=>array('!=',$special_date)) which correctly becomes SQL >>
851
-     *                                        "PAY_timestamp > 123412341 AND PAY_timestamp < 2354235235234 AND
852
-     *                                        PAY_timestamp != 1241234123" This can be applied to condition operators
853
-     *                                        too, eg:
854
-     *                                        array('OR'=>array('REG_ID'=>3,'Transaction.TXN_ID'=>23),'OR*whatever'=>array('Attendee.ATT_fname'=>'bob','Attendee.ATT_lname'=>'wilson')));
855
-     * @var mixed   $limit                    int|array    adds a limit to the query just like the SQL limit clause, so
856
-     *                                        limits of "23", "25,50", and array(23,42) are all valid would become SQL
857
-     *                                        "...LIMIT 23", "...LIMIT 25,50", and "...LIMIT 23,42" respectively.
858
-     *                                        Remember when you provide two numbers for the limit, the 1st number is
859
-     *                                        the OFFSET, the 2nd is the LIMIT
860
-     * @var array   $on_join_limit            allows the setting of a special select join with a internal limit so you
861
-     *                                        can do paging on one-to-many multi-table-joins. Send an array in the
862
-     *                                        following format array('on_join_limit'
863
-     *                                        => array( 'table_alias', array(1,2) ) ).
864
-     * @var mixed   $order_by                 name of a column to order by, or an array where keys are field names and
865
-     *                                        values are either 'ASC' or 'DESC'.
866
-     *                                        'limit'=>array('STS_ID'=>'ASC','REG_date'=>'DESC'), which would becomes
867
-     *                                        SQL "...ORDER BY TXN_timestamp..." and "...ORDER BY STS_ID ASC, REG_date
868
-     *                                        DESC..." respectively. Like the
869
-     *                                        'where' conditions, these fields can be on related models. Eg
870
-     *                                        'order_by'=>array('Registration.Transaction.TXN_amount'=>'ASC') is
871
-     *                                        perfectly valid from any model related to 'Registration' (like Event,
872
-     *                                        Attendee, Price, Datetime, etc.)
873
-     * @var string  $order                    If 'order_by' is used and its value is a string (NOT an array), then
874
-     *                                        'order' specifies whether to order the field specified in 'order_by' in
875
-     *                                        ascending or descending order. Acceptable values are 'ASC' or 'DESC'. If,
876
-     *                                        'order_by' isn't used, but 'order' is, then it is assumed you want to
877
-     *                                        order by the primary key. Eg,
878
-     *                                        EEM_Event::instance()->get_all(array('order_by'=>'Datetime.DTT_EVT_start','order'=>'ASC');
879
-     *                                        //(will join with the Datetime model's table(s) and order by its field
880
-     *                                        DTT_EVT_start) or
881
-     *                                        EEM_Registration::instance()->get_all(array('order'=>'ASC'));//will make
882
-     *                                        SQL "SELECT * FROM wp_esp_registration ORDER BY REG_ID ASC"
883
-     * @var mixed   $group_by                 name of field to order by, or an array of fields. Eg either
884
-     *                                        'group_by'=>'VNU_ID', or
885
-     *                                        'group_by'=>array('EVT_name','Registration.Transaction.TXN_total') Note:
886
-     *                                        if no
887
-     *                                        $group_by is specified, and a limit is set, automatically groups by the
888
-     *                                        model's primary key (or combined primary keys). This avoids some
889
-     *                                        weirdness that results when using limits, tons of joins, and no group by,
890
-     *                                        see https://events.codebasehq.com/projects/event-espresso/tickets/9389
891
-     * @var array   $having                   exactly like WHERE parameters array, except these conditions apply to the
892
-     *                                        grouped results (whereas WHERE conditions apply to the pre-grouped
893
-     *                                        results)
894
-     * @var array   $force_join               forces a join with the models named. Should be a numerically-indexed
895
-     *                                        array where values are models to be joined in the query.Eg
896
-     *                                        array('Attendee','Payment','Datetime'). You may join with transient
897
-     *                                        models using period, eg "Registration.Transaction.Payment". You will
898
-     *                                        probably only want to do this in hopes of increasing efficiency, as
899
-     *                                        related models which belongs to the current model
900
-     *                                        (ie, the current model has a foreign key to them, like how Registration
901
-     *                                        belongs to Attendee) can be cached in order to avoid future queries
902
-     * @var string  $default_where_conditions can be set to 'none', 'this_model_only', 'other_models_only', or 'all'.
903
-     *                                        set this to 'none' to disable all default where conditions. Eg, usually
904
-     *                                        soft-deleted objects are filtered-out if you want to include them, set
905
-     *                                        this query param to 'none'. If you want to ONLY disable THIS model's
906
-     *                                        default where conditions set it to 'other_models_only'. If you only want
907
-     *                                        this model's default where conditions added to the query, use
908
-     *                                        'this_model_only'. If you want to use all default where conditions
909
-     *                                        (default), set to 'all'.
910
-     * @var string  $caps                     controls what capability requirements to apply to the query; ie, should
911
-     *                                        we just NOT apply any capabilities/permissions/restrictions and return
912
-     *                                        everything? Or should we only show the current user items they should be
913
-     *                                        able to view on the frontend, backend, edit, or delete? can be set to
914
-     *                                        'none' (default), 'read_frontend', 'read_backend', 'edit' or 'delete'
915
-     *                                        }
916
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
917
-     *                                        from EE_Base_Class[], use _get_all_wpdb_results()and make it public
918
-     *                                        again. Array keys are object IDs (if there is a primary key on the model.
919
-     *                                        if not, numerically indexed) Some full examples: get 10 transactions
920
-     *                                        which have Scottish attendees: EEM_Transaction::instance()->get_all(
921
-     *                                        array( array(
922
-     *                                        'OR'=>array(
923
-     *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
924
-     *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
925
-     *                                        )
926
-     *                                        ),
927
-     *                                        'limit'=>10,
928
-     *                                        'group_by'=>'TXN_ID'
929
-     *                                        ));
930
-     *                                        get all the answers to the question titled "shirt size" for event with id
931
-     *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
932
-     *                                        'Question.QST_display_text'=>'shirt size',
933
-     *                                        'Registration.Event.EVT_ID'=>12
934
-     *                                        ),
935
-     *                                        'order_by'=>array('ANS_value'=>'ASC')
936
-     *                                        ));
937
-     * @throws EE_Error
938
-     */
939
-    public function get_all($query_params = array())
940
-    {
941
-        if (isset($query_params['limit'])
942
-            && ! isset($query_params['group_by'])
943
-        ) {
944
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
945
-        }
946
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params, ARRAY_A, null));
947
-    }
948
-
949
-
950
-
951
-    /**
952
-     * Modifies the query parameters so we only get back model objects
953
-     * that "belong" to the current user
954
-     *
955
-     * @param array $query_params @see EEM_Base::get_all()
956
-     * @return array like EEM_Base::get_all
957
-     */
958
-    public function alter_query_params_to_only_include_mine($query_params = array())
959
-    {
960
-        $wp_user_field_name = $this->wp_user_field_name();
961
-        if ($wp_user_field_name) {
962
-            $query_params[0][$wp_user_field_name] = get_current_user_id();
963
-        }
964
-        return $query_params;
965
-    }
966
-
967
-
968
-
969
-    /**
970
-     * Returns the name of the field's name that points to the WP_User table
971
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
972
-     * foreign key to the WP_User table)
973
-     *
974
-     * @return string|boolean string on success, boolean false when there is no
975
-     * foreign key to the WP_User table
976
-     */
977
-    public function wp_user_field_name()
978
-    {
979
-        try {
980
-            if (! empty($this->_model_chain_to_wp_user)) {
981
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
982
-                $last_model_name = end($models_to_follow_to_wp_users);
983
-                $model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
984
-                $model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
985
-            } else {
986
-                $model_with_fk_to_wp_users = $this;
987
-                $model_chain_to_wp_user = '';
988
-            }
989
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
990
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
991
-        } catch (EE_Error $e) {
992
-            return false;
993
-        }
994
-    }
995
-
996
-
997
-
998
-    /**
999
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
1000
-     * (or transiently-related model) has a foreign key to the wp_users table;
1001
-     * useful for finding if model objects of this type are 'owned' by the current user.
1002
-     * This is an empty string when the foreign key is on this model and when it isn't,
1003
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
1004
-     * (or transiently-related model)
1005
-     *
1006
-     * @return string
1007
-     */
1008
-    public function model_chain_to_wp_user()
1009
-    {
1010
-        return $this->_model_chain_to_wp_user;
1011
-    }
1012
-
1013
-
1014
-
1015
-    /**
1016
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1017
-     * like how registrations don't have a foreign key to wp_users, but the
1018
-     * events they are for are), or is unrelated to wp users.
1019
-     * generally available
1020
-     *
1021
-     * @return boolean
1022
-     */
1023
-    public function is_owned()
1024
-    {
1025
-        if ($this->model_chain_to_wp_user()) {
1026
-            return true;
1027
-        }
1028
-        try {
1029
-            $this->get_foreign_key_to('WP_User');
1030
-            return true;
1031
-        } catch (EE_Error $e) {
1032
-            return false;
1033
-        }
1034
-    }
1035
-
1036
-
1037
-    /**
1038
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1039
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1040
-     * the model)
1041
-     *
1042
-     * @param array  $query_params      like EEM_Base::get_all's $query_params
1043
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1044
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1045
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1046
-     *                                  override this and set the select to "*", or a specific column name, like
1047
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1048
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1049
-     *                                  the aliases used to refer to this selection, and values are to be
1050
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1051
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1052
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1053
-     * @throws EE_Error
1054
-     * @throws InvalidArgumentException
1055
-     */
1056
-    protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1057
-    {
1058
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);;
1059
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1060
-        $select_expressions = $columns_to_select === null
1061
-            ? $this->_construct_default_select_sql($model_query_info)
1062
-            : '';
1063
-        if ($this->_custom_selections instanceof CustomSelects) {
1064
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1065
-            $select_expressions .= $select_expressions
1066
-                ? ', ' . $custom_expressions
1067
-                : $custom_expressions;
1068
-        }
1069
-
1070
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1071
-        return $this->_do_wpdb_query('get_results', array($SQL, $output));
1072
-    }
1073
-
1074
-
1075
-    /**
1076
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1077
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1078
-     * method of including extra select information.
1079
-     *
1080
-     * @param array             $query_params
1081
-     * @param null|array|string $columns_to_select
1082
-     * @return null|CustomSelects
1083
-     * @throws InvalidArgumentException
1084
-     */
1085
-    protected function getCustomSelection(array $query_params, $columns_to_select = null)
1086
-    {
1087
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1088
-            return null;
1089
-        }
1090
-        $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1091
-        $selects = is_string($selects) ? explode(',', $selects) : $selects;
1092
-        return new CustomSelects($selects);
1093
-    }
1094
-
1095
-
1096
-
1097
-    /**
1098
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1099
-     * but you can use the $query_params like on EEM_Base::get_all() to more easily
1100
-     * take care of joins, field preparation etc.
1101
-     *
1102
-     * @param array  $query_params      like EEM_Base::get_all's $query_params
1103
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1104
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1105
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1106
-     *                                  override this and set the select to "*", or a specific column name, like
1107
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1108
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1109
-     *                                  the aliases used to refer to this selection, and values are to be
1110
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1111
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1112
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1113
-     * @throws EE_Error
1114
-     */
1115
-    public function get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1116
-    {
1117
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1118
-    }
1119
-
1120
-
1121
-
1122
-    /**
1123
-     * For creating a custom select statement
1124
-     *
1125
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1126
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1127
-     *                                 SQL, and 1=>is the datatype
1128
-     * @throws EE_Error
1129
-     * @return string
1130
-     */
1131
-    private function _construct_select_from_input($columns_to_select)
1132
-    {
1133
-        if (is_array($columns_to_select)) {
1134
-            $select_sql_array = array();
1135
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1136
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1137
-                    throw new EE_Error(
1138
-                        sprintf(
1139
-                            __(
1140
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1141
-                                'event_espresso'
1142
-                            ),
1143
-                            $selection_and_datatype,
1144
-                            $alias
1145
-                        )
1146
-                    );
1147
-                }
1148
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1149
-                    throw new EE_Error(
1150
-                        sprintf(
1151
-                            esc_html__(
1152
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1153
-                                'event_espresso'
1154
-                            ),
1155
-                            $selection_and_datatype[1],
1156
-                            $selection_and_datatype[0],
1157
-                            $alias,
1158
-                            implode(', ', $this->_valid_wpdb_data_types)
1159
-                        )
1160
-                    );
1161
-                }
1162
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1163
-            }
1164
-            $columns_to_select_string = implode(', ', $select_sql_array);
1165
-        } else {
1166
-            $columns_to_select_string = $columns_to_select;
1167
-        }
1168
-        return $columns_to_select_string;
1169
-    }
1170
-
1171
-
1172
-
1173
-    /**
1174
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1175
-     *
1176
-     * @return string
1177
-     * @throws EE_Error
1178
-     */
1179
-    public function primary_key_name()
1180
-    {
1181
-        return $this->get_primary_key_field()->get_name();
1182
-    }
1183
-
1184
-
1185
-
1186
-    /**
1187
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1188
-     * If there is no primary key on this model, $id is treated as primary key string
1189
-     *
1190
-     * @param mixed $id int or string, depending on the type of the model's primary key
1191
-     * @return EE_Base_Class
1192
-     */
1193
-    public function get_one_by_ID($id)
1194
-    {
1195
-        if ($this->get_from_entity_map($id)) {
1196
-            return $this->get_from_entity_map($id);
1197
-        }
1198
-        return $this->get_one(
1199
-            $this->alter_query_params_to_restrict_by_ID(
1200
-                $id,
1201
-                array('default_where_conditions' => EEM_Base::default_where_conditions_minimum_all)
1202
-            )
1203
-        );
1204
-    }
1205
-
1206
-
1207
-
1208
-    /**
1209
-     * Alters query parameters to only get items with this ID are returned.
1210
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1211
-     * or could just be a simple primary key ID
1212
-     *
1213
-     * @param int   $id
1214
-     * @param array $query_params
1215
-     * @return array of normal query params, @see EEM_Base::get_all
1216
-     * @throws EE_Error
1217
-     */
1218
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1219
-    {
1220
-        if (! isset($query_params[0])) {
1221
-            $query_params[0] = array();
1222
-        }
1223
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1224
-        if ($conditions_from_id === null) {
1225
-            $query_params[0][$this->primary_key_name()] = $id;
1226
-        } else {
1227
-            //no primary key, so the $id must be from the get_index_primary_key_string()
1228
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1229
-        }
1230
-        return $query_params;
1231
-    }
1232
-
1233
-
1234
-
1235
-    /**
1236
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1237
-     * array. If no item is found, null is returned.
1238
-     *
1239
-     * @param array $query_params like EEM_Base's $query_params variable.
1240
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1241
-     * @throws EE_Error
1242
-     */
1243
-    public function get_one($query_params = array())
1244
-    {
1245
-        if (! is_array($query_params)) {
1246
-            EE_Error::doing_it_wrong('EEM_Base::get_one',
1247
-                sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1248
-                    gettype($query_params)), '4.6.0');
1249
-            $query_params = array();
1250
-        }
1251
-        $query_params['limit'] = 1;
1252
-        $items = $this->get_all($query_params);
1253
-        if (empty($items)) {
1254
-            return null;
1255
-        }
1256
-        return array_shift($items);
1257
-    }
1258
-
1259
-
1260
-
1261
-    /**
1262
-     * Returns the next x number of items in sequence from the given value as
1263
-     * found in the database matching the given query conditions.
1264
-     *
1265
-     * @param mixed $current_field_value    Value used for the reference point.
1266
-     * @param null  $field_to_order_by      What field is used for the
1267
-     *                                      reference point.
1268
-     * @param int   $limit                  How many to return.
1269
-     * @param array $query_params           Extra conditions on the query.
1270
-     * @param null  $columns_to_select      If left null, then an array of
1271
-     *                                      EE_Base_Class objects is returned,
1272
-     *                                      otherwise you can indicate just the
1273
-     *                                      columns you want returned.
1274
-     * @return EE_Base_Class[]|array
1275
-     * @throws EE_Error
1276
-     */
1277
-    public function next_x(
1278
-        $current_field_value,
1279
-        $field_to_order_by = null,
1280
-        $limit = 1,
1281
-        $query_params = array(),
1282
-        $columns_to_select = null
1283
-    ) {
1284
-        return $this->_get_consecutive(
1285
-            $current_field_value,
1286
-            '>',
1287
-            $field_to_order_by,
1288
-            $limit,
1289
-            $query_params,
1290
-            $columns_to_select
1291
-        );
1292
-    }
1293
-
1294
-
1295
-
1296
-    /**
1297
-     * Returns the previous x number of items in sequence from the given value
1298
-     * as found in the database matching the given query conditions.
1299
-     *
1300
-     * @param mixed $current_field_value    Value used for the reference point.
1301
-     * @param null  $field_to_order_by      What field is used for the
1302
-     *                                      reference point.
1303
-     * @param int   $limit                  How many to return.
1304
-     * @param array $query_params           Extra conditions on the query.
1305
-     * @param null  $columns_to_select      If left null, then an array of
1306
-     *                                      EE_Base_Class objects is returned,
1307
-     *                                      otherwise you can indicate just the
1308
-     *                                      columns you want returned.
1309
-     * @return EE_Base_Class[]|array
1310
-     * @throws EE_Error
1311
-     */
1312
-    public function previous_x(
1313
-        $current_field_value,
1314
-        $field_to_order_by = null,
1315
-        $limit = 1,
1316
-        $query_params = array(),
1317
-        $columns_to_select = null
1318
-    ) {
1319
-        return $this->_get_consecutive(
1320
-            $current_field_value,
1321
-            '<',
1322
-            $field_to_order_by,
1323
-            $limit,
1324
-            $query_params,
1325
-            $columns_to_select
1326
-        );
1327
-    }
1328
-
1329
-
1330
-
1331
-    /**
1332
-     * Returns the next item in sequence from the given value as found in the
1333
-     * database matching the given query conditions.
1334
-     *
1335
-     * @param mixed $current_field_value    Value used for the reference point.
1336
-     * @param null  $field_to_order_by      What field is used for the
1337
-     *                                      reference point.
1338
-     * @param array $query_params           Extra conditions on the query.
1339
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1340
-     *                                      object is returned, otherwise you
1341
-     *                                      can indicate just the columns you
1342
-     *                                      want and a single array indexed by
1343
-     *                                      the columns will be returned.
1344
-     * @return EE_Base_Class|null|array()
1345
-     * @throws EE_Error
1346
-     */
1347
-    public function next(
1348
-        $current_field_value,
1349
-        $field_to_order_by = null,
1350
-        $query_params = array(),
1351
-        $columns_to_select = null
1352
-    ) {
1353
-        $results = $this->_get_consecutive(
1354
-            $current_field_value,
1355
-            '>',
1356
-            $field_to_order_by,
1357
-            1,
1358
-            $query_params,
1359
-            $columns_to_select
1360
-        );
1361
-        return empty($results) ? null : reset($results);
1362
-    }
1363
-
1364
-
1365
-
1366
-    /**
1367
-     * Returns the previous item in sequence from the given value as found in
1368
-     * the database matching the given query conditions.
1369
-     *
1370
-     * @param mixed $current_field_value    Value used for the reference point.
1371
-     * @param null  $field_to_order_by      What field is used for the
1372
-     *                                      reference point.
1373
-     * @param array $query_params           Extra conditions on the query.
1374
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1375
-     *                                      object is returned, otherwise you
1376
-     *                                      can indicate just the columns you
1377
-     *                                      want and a single array indexed by
1378
-     *                                      the columns will be returned.
1379
-     * @return EE_Base_Class|null|array()
1380
-     * @throws EE_Error
1381
-     */
1382
-    public function previous(
1383
-        $current_field_value,
1384
-        $field_to_order_by = null,
1385
-        $query_params = array(),
1386
-        $columns_to_select = null
1387
-    ) {
1388
-        $results = $this->_get_consecutive(
1389
-            $current_field_value,
1390
-            '<',
1391
-            $field_to_order_by,
1392
-            1,
1393
-            $query_params,
1394
-            $columns_to_select
1395
-        );
1396
-        return empty($results) ? null : reset($results);
1397
-    }
1398
-
1399
-
1400
-
1401
-    /**
1402
-     * Returns the a consecutive number of items in sequence from the given
1403
-     * value as found in the database matching the given query conditions.
1404
-     *
1405
-     * @param mixed  $current_field_value   Value used for the reference point.
1406
-     * @param string $operand               What operand is used for the sequence.
1407
-     * @param string $field_to_order_by     What field is used for the reference point.
1408
-     * @param int    $limit                 How many to return.
1409
-     * @param array  $query_params          Extra conditions on the query.
1410
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1411
-     *                                      otherwise you can indicate just the columns you want returned.
1412
-     * @return EE_Base_Class[]|array
1413
-     * @throws EE_Error
1414
-     */
1415
-    protected function _get_consecutive(
1416
-        $current_field_value,
1417
-        $operand = '>',
1418
-        $field_to_order_by = null,
1419
-        $limit = 1,
1420
-        $query_params = array(),
1421
-        $columns_to_select = null
1422
-    ) {
1423
-        //if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1424
-        if (empty($field_to_order_by)) {
1425
-            if ($this->has_primary_key_field()) {
1426
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1427
-            } else {
1428
-                if (WP_DEBUG) {
1429
-                    throw new EE_Error(__('EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1430
-                        'event_espresso'));
1431
-                }
1432
-                EE_Error::add_error(__('There was an error with the query.', 'event_espresso'));
1433
-                return array();
1434
-            }
1435
-        }
1436
-        if (! is_array($query_params)) {
1437
-            EE_Error::doing_it_wrong('EEM_Base::_get_consecutive',
1438
-                sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1439
-                    gettype($query_params)), '4.6.0');
1440
-            $query_params = array();
1441
-        }
1442
-        //let's add the where query param for consecutive look up.
1443
-        $query_params[0][$field_to_order_by] = array($operand, $current_field_value);
1444
-        $query_params['limit'] = $limit;
1445
-        //set direction
1446
-        $incoming_orderby = isset($query_params['order_by']) ? (array)$query_params['order_by'] : array();
1447
-        $query_params['order_by'] = $operand === '>'
1448
-            ? array($field_to_order_by => 'ASC') + $incoming_orderby
1449
-            : array($field_to_order_by => 'DESC') + $incoming_orderby;
1450
-        //if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1451
-        if (empty($columns_to_select)) {
1452
-            return $this->get_all($query_params);
1453
-        }
1454
-        //getting just the fields
1455
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1456
-    }
1457
-
1458
-
1459
-
1460
-    /**
1461
-     * This sets the _timezone property after model object has been instantiated.
1462
-     *
1463
-     * @param null | string $timezone valid PHP DateTimeZone timezone string
1464
-     */
1465
-    public function set_timezone($timezone)
1466
-    {
1467
-        if ($timezone !== null) {
1468
-            $this->_timezone = $timezone;
1469
-        }
1470
-        //note we need to loop through relations and set the timezone on those objects as well.
1471
-        foreach ($this->_model_relations as $relation) {
1472
-            $relation->set_timezone($timezone);
1473
-        }
1474
-        //and finally we do the same for any datetime fields
1475
-        foreach ($this->_fields as $field) {
1476
-            if ($field instanceof EE_Datetime_Field) {
1477
-                $field->set_timezone($timezone);
1478
-            }
1479
-        }
1480
-    }
1481
-
1482
-
1483
-
1484
-    /**
1485
-     * This just returns whatever is set for the current timezone.
1486
-     *
1487
-     * @access public
1488
-     * @return string
1489
-     */
1490
-    public function get_timezone()
1491
-    {
1492
-        //first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1493
-        if (empty($this->_timezone)) {
1494
-            foreach ($this->_fields as $field) {
1495
-                if ($field instanceof EE_Datetime_Field) {
1496
-                    $this->set_timezone($field->get_timezone());
1497
-                    break;
1498
-                }
1499
-            }
1500
-        }
1501
-        //if timezone STILL empty then return the default timezone for the site.
1502
-        if (empty($this->_timezone)) {
1503
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1504
-        }
1505
-        return $this->_timezone;
1506
-    }
1507
-
1508
-
1509
-
1510
-    /**
1511
-     * This returns the date formats set for the given field name and also ensures that
1512
-     * $this->_timezone property is set correctly.
1513
-     *
1514
-     * @since 4.6.x
1515
-     * @param string $field_name The name of the field the formats are being retrieved for.
1516
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1517
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1518
-     * @return array formats in an array with the date format first, and the time format last.
1519
-     */
1520
-    public function get_formats_for($field_name, $pretty = false)
1521
-    {
1522
-        $field_settings = $this->field_settings_for($field_name);
1523
-        //if not a valid EE_Datetime_Field then throw error
1524
-        if (! $field_settings instanceof EE_Datetime_Field) {
1525
-            throw new EE_Error(sprintf(__('The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1526
-                'event_espresso'), $field_name));
1527
-        }
1528
-        //while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1529
-        //the field.
1530
-        $this->_timezone = $field_settings->get_timezone();
1531
-        return array($field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty));
1532
-    }
1533
-
1534
-
1535
-
1536
-    /**
1537
-     * This returns the current time in a format setup for a query on this model.
1538
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1539
-     * it will return:
1540
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1541
-     *  NOW
1542
-     *  - or a unix timestamp (equivalent to time())
1543
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1544
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1545
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1546
-     * @since 4.6.x
1547
-     * @param string $field_name       The field the current time is needed for.
1548
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1549
-     *                                 formatted string matching the set format for the field in the set timezone will
1550
-     *                                 be returned.
1551
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1552
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1553
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1554
-     *                                 exception is triggered.
1555
-     */
1556
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1557
-    {
1558
-        $formats = $this->get_formats_for($field_name);
1559
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1560
-        if ($timestamp) {
1561
-            return $DateTime->format('U');
1562
-        }
1563
-        //not returning timestamp, so return formatted string in timezone.
1564
-        switch ($what) {
1565
-            case 'time' :
1566
-                return $DateTime->format($formats[1]);
1567
-                break;
1568
-            case 'date' :
1569
-                return $DateTime->format($formats[0]);
1570
-                break;
1571
-            default :
1572
-                return $DateTime->format(implode(' ', $formats));
1573
-                break;
1574
-        }
1575
-    }
1576
-
1577
-
1578
-
1579
-    /**
1580
-     * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1581
-     * for the model are.  Returns a DateTime object.
1582
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1583
-     * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1584
-     * ignored.
1585
-     *
1586
-     * @param string $field_name      The field being setup.
1587
-     * @param string $timestring      The date time string being used.
1588
-     * @param string $incoming_format The format for the time string.
1589
-     * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1590
-     *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1591
-     *                                format is
1592
-     *                                'U', this is ignored.
1593
-     * @return DateTime
1594
-     * @throws EE_Error
1595
-     */
1596
-    public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1597
-    {
1598
-        //just using this to ensure the timezone is set correctly internally
1599
-        $this->get_formats_for($field_name);
1600
-        //load EEH_DTT_Helper
1601
-        $set_timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1602
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1603
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1604
-        return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1605
-    }
1606
-
1607
-
1608
-
1609
-    /**
1610
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1611
-     *
1612
-     * @return EE_Table_Base[]
1613
-     */
1614
-    public function get_tables()
1615
-    {
1616
-        return $this->_tables;
1617
-    }
1618
-
1619
-
1620
-
1621
-    /**
1622
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1623
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1624
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1625
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1626
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1627
-     * model object with EVT_ID = 1
1628
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1629
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1630
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1631
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1632
-     * are not specified)
1633
-     *
1634
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1635
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1636
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1637
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1638
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1639
-     *                                         ID=34, we'd use this method as follows:
1640
-     *                                         EEM_Transaction::instance()->update(
1641
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1642
-     *                                         array(array('TXN_ID'=>34)));
1643
-     * @param array   $query_params            very much like EEM_Base::get_all's $query_params
1644
-     *                                         in client code into what's expected to be stored on each field. Eg,
1645
-     *                                         consider updating Question's QST_admin_label field is of type
1646
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1647
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1648
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1649
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1650
-     *                                         TRUE, it is assumed that you've already called
1651
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1652
-     *                                         malicious javascript. However, if
1653
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1654
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1655
-     *                                         and every other field, before insertion. We provide this parameter
1656
-     *                                         because model objects perform their prepare_for_set function on all
1657
-     *                                         their values, and so don't need to be called again (and in many cases,
1658
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1659
-     *                                         prepare_for_set method...)
1660
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1661
-     *                                         in this model's entity map according to $fields_n_values that match
1662
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1663
-     *                                         by setting this to FALSE, but be aware that model objects being used
1664
-     *                                         could get out-of-sync with the database
1665
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1666
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1667
-     *                                         bad)
1668
-     * @throws EE_Error
1669
-     */
1670
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1671
-    {
1672
-        if (! is_array($query_params)) {
1673
-            EE_Error::doing_it_wrong('EEM_Base::update',
1674
-                sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1675
-                    gettype($query_params)), '4.6.0');
1676
-            $query_params = array();
1677
-        }
1678
-        /**
1679
-         * Action called before a model update call has been made.
1680
-         *
1681
-         * @param EEM_Base $model
1682
-         * @param array    $fields_n_values the updated fields and their new values
1683
-         * @param array    $query_params    @see EEM_Base::get_all()
1684
-         */
1685
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1686
-        /**
1687
-         * Filters the fields about to be updated given the query parameters. You can provide the
1688
-         * $query_params to $this->get_all() to find exactly which records will be updated
1689
-         *
1690
-         * @param array    $fields_n_values fields and their new values
1691
-         * @param EEM_Base $model           the model being queried
1692
-         * @param array    $query_params    see EEM_Base::get_all()
1693
-         */
1694
-        $fields_n_values = (array)apply_filters('FHEE__EEM_Base__update__fields_n_values', $fields_n_values, $this,
1695
-            $query_params);
1696
-        //need to verify that, for any entry we want to update, there are entries in each secondary table.
1697
-        //to do that, for each table, verify that it's PK isn't null.
1698
-        $tables = $this->get_tables();
1699
-        //and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1700
-        //NOTE: we should make this code more efficient by NOT querying twice
1701
-        //before the real update, but that needs to first go through ALPHA testing
1702
-        //as it's dangerous. says Mike August 8 2014
1703
-        //we want to make sure the default_where strategy is ignored
1704
-        $this->_ignore_where_strategy = true;
1705
-        $wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1706
-        foreach ($wpdb_select_results as $wpdb_result) {
1707
-            // type cast stdClass as array
1708
-            $wpdb_result = (array)$wpdb_result;
1709
-            //get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1710
-            if ($this->has_primary_key_field()) {
1711
-                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1712
-            } else {
1713
-                //if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1714
-                $main_table_pk_value = null;
1715
-            }
1716
-            //if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1717
-            //and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1718
-            if (count($tables) > 1) {
1719
-                //foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1720
-                //in that table, and so we'll want to insert one
1721
-                foreach ($tables as $table_obj) {
1722
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1723
-                    //if there is no private key for this table on the results, it means there's no entry
1724
-                    //in this table, right? so insert a row in the current table, using any fields available
1725
-                    if (! (array_key_exists($this_table_pk_column, $wpdb_result)
1726
-                           && $wpdb_result[$this_table_pk_column])
1727
-                    ) {
1728
-                        $success = $this->_insert_into_specific_table($table_obj, $fields_n_values,
1729
-                            $main_table_pk_value);
1730
-                        //if we died here, report the error
1731
-                        if (! $success) {
1732
-                            return false;
1733
-                        }
1734
-                    }
1735
-                }
1736
-            }
1737
-            //				//and now check that if we have cached any models by that ID on the model, that
1738
-            //				//they also get updated properly
1739
-            //				$model_object = $this->get_from_entity_map( $main_table_pk_value );
1740
-            //				if( $model_object ){
1741
-            //					foreach( $fields_n_values as $field => $value ){
1742
-            //						$model_object->set($field, $value);
1743
-            //let's make sure default_where strategy is followed now
1744
-            $this->_ignore_where_strategy = false;
1745
-        }
1746
-        //if we want to keep model objects in sync, AND
1747
-        //if this wasn't called from a model object (to update itself)
1748
-        //then we want to make sure we keep all the existing
1749
-        //model objects in sync with the db
1750
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1751
-            if ($this->has_primary_key_field()) {
1752
-                $model_objs_affected_ids = $this->get_col($query_params);
1753
-            } else {
1754
-                //we need to select a bunch of columns and then combine them into the the "index primary key string"s
1755
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1756
-                $model_objs_affected_ids = array();
1757
-                foreach ($models_affected_key_columns as $row) {
1758
-                    $combined_index_key = $this->get_index_primary_key_string($row);
1759
-                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1760
-                }
1761
-            }
1762
-            if (! $model_objs_affected_ids) {
1763
-                //wait wait wait- if nothing was affected let's stop here
1764
-                return 0;
1765
-            }
1766
-            foreach ($model_objs_affected_ids as $id) {
1767
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1768
-                if ($model_obj_in_entity_map) {
1769
-                    foreach ($fields_n_values as $field => $new_value) {
1770
-                        $model_obj_in_entity_map->set($field, $new_value);
1771
-                    }
1772
-                }
1773
-            }
1774
-            //if there is a primary key on this model, we can now do a slight optimization
1775
-            if ($this->has_primary_key_field()) {
1776
-                //we already know what we want to update. So let's make the query simpler so it's a little more efficient
1777
-                $query_params = array(
1778
-                    array($this->primary_key_name() => array('IN', $model_objs_affected_ids)),
1779
-                    'limit'                    => count($model_objs_affected_ids),
1780
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1781
-                );
1782
-            }
1783
-        }
1784
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1785
-        $SQL = "UPDATE "
1786
-               . $model_query_info->get_full_join_sql()
1787
-               . " SET "
1788
-               . $this->_construct_update_sql($fields_n_values)
1789
-               . $model_query_info->get_where_sql();//note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1790
-        $rows_affected = $this->_do_wpdb_query('query', array($SQL));
1791
-        /**
1792
-         * Action called after a model update call has been made.
1793
-         *
1794
-         * @param EEM_Base $model
1795
-         * @param array    $fields_n_values the updated fields and their new values
1796
-         * @param array    $query_params    @see EEM_Base::get_all()
1797
-         * @param int      $rows_affected
1798
-         */
1799
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1800
-        return $rows_affected;//how many supposedly got updated
1801
-    }
1802
-
1803
-
1804
-
1805
-    /**
1806
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1807
-     * are teh values of the field specified (or by default the primary key field)
1808
-     * that matched the query params. Note that you should pass the name of the
1809
-     * model FIELD, not the database table's column name.
1810
-     *
1811
-     * @param array  $query_params @see EEM_Base::get_all()
1812
-     * @param string $field_to_select
1813
-     * @return array just like $wpdb->get_col()
1814
-     * @throws EE_Error
1815
-     */
1816
-    public function get_col($query_params = array(), $field_to_select = null)
1817
-    {
1818
-        if ($field_to_select) {
1819
-            $field = $this->field_settings_for($field_to_select);
1820
-        } elseif ($this->has_primary_key_field()) {
1821
-            $field = $this->get_primary_key_field();
1822
-        } else {
1823
-            //no primary key, just grab the first column
1824
-            $field = reset($this->field_settings());
1825
-        }
1826
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1827
-        $select_expressions = $field->get_qualified_column();
1828
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1829
-        return $this->_do_wpdb_query('get_col', array($SQL));
1830
-    }
1831
-
1832
-
1833
-
1834
-    /**
1835
-     * Returns a single column value for a single row from the database
1836
-     *
1837
-     * @param array  $query_params    @see EEM_Base::get_all()
1838
-     * @param string $field_to_select @see EEM_Base::get_col()
1839
-     * @return string
1840
-     * @throws EE_Error
1841
-     */
1842
-    public function get_var($query_params = array(), $field_to_select = null)
1843
-    {
1844
-        $query_params['limit'] = 1;
1845
-        $col = $this->get_col($query_params, $field_to_select);
1846
-        if (! empty($col)) {
1847
-            return reset($col);
1848
-        }
1849
-        return null;
1850
-    }
1851
-
1852
-
1853
-
1854
-    /**
1855
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1856
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1857
-     * injection, but currently no further filtering is done
1858
-     *
1859
-     * @global      $wpdb
1860
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1861
-     *                               be updated to in the DB
1862
-     * @return string of SQL
1863
-     * @throws EE_Error
1864
-     */
1865
-    public function _construct_update_sql($fields_n_values)
1866
-    {
1867
-        /** @type WPDB $wpdb */
1868
-        global $wpdb;
1869
-        $cols_n_values = array();
1870
-        foreach ($fields_n_values as $field_name => $value) {
1871
-            $field_obj = $this->field_settings_for($field_name);
1872
-            //if the value is NULL, we want to assign the value to that.
1873
-            //wpdb->prepare doesn't really handle that properly
1874
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1875
-            $value_sql = $prepared_value === null ? 'NULL'
1876
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1877
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1878
-        }
1879
-        return implode(",", $cols_n_values);
1880
-    }
1881
-
1882
-
1883
-
1884
-    /**
1885
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1886
-     * Performs a HARD delete, meaning the database row should always be removed,
1887
-     * not just have a flag field on it switched
1888
-     * Wrapper for EEM_Base::delete_permanently()
1889
-     *
1890
-     * @param mixed $id
1891
-     * @param boolean $allow_blocking
1892
-     * @return int the number of rows deleted
1893
-     * @throws EE_Error
1894
-     */
1895
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1896
-    {
1897
-        return $this->delete_permanently(
1898
-            array(
1899
-                array($this->get_primary_key_field()->get_name() => $id),
1900
-                'limit' => 1,
1901
-            ),
1902
-            $allow_blocking
1903
-        );
1904
-    }
1905
-
1906
-
1907
-
1908
-    /**
1909
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1910
-     * Wrapper for EEM_Base::delete()
1911
-     *
1912
-     * @param mixed $id
1913
-     * @param boolean $allow_blocking
1914
-     * @return int the number of rows deleted
1915
-     * @throws EE_Error
1916
-     */
1917
-    public function delete_by_ID($id, $allow_blocking = true)
1918
-    {
1919
-        return $this->delete(
1920
-            array(
1921
-                array($this->get_primary_key_field()->get_name() => $id),
1922
-                'limit' => 1,
1923
-            ),
1924
-            $allow_blocking
1925
-        );
1926
-    }
1927
-
1928
-
1929
-
1930
-    /**
1931
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1932
-     * meaning if the model has a field that indicates its been "trashed" or
1933
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1934
-     *
1935
-     * @see EEM_Base::delete_permanently
1936
-     * @param array   $query_params
1937
-     * @param boolean $allow_blocking
1938
-     * @return int how many rows got deleted
1939
-     * @throws EE_Error
1940
-     */
1941
-    public function delete($query_params, $allow_blocking = true)
1942
-    {
1943
-        return $this->delete_permanently($query_params, $allow_blocking);
1944
-    }
1945
-
1946
-
1947
-
1948
-    /**
1949
-     * Deletes the model objects that meet the query params. Note: this method is overridden
1950
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1951
-     * as archived, not actually deleted
1952
-     *
1953
-     * @param array   $query_params   very much like EEM_Base::get_all's $query_params
1954
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1955
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1956
-     *                                deletes regardless of other objects which may depend on it. Its generally
1957
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1958
-     *                                DB
1959
-     * @return int how many rows got deleted
1960
-     * @throws EE_Error
1961
-     */
1962
-    public function delete_permanently($query_params, $allow_blocking = true)
1963
-    {
1964
-        /**
1965
-         * Action called just before performing a real deletion query. You can use the
1966
-         * model and its $query_params to find exactly which items will be deleted
1967
-         *
1968
-         * @param EEM_Base $model
1969
-         * @param array    $query_params   @see EEM_Base::get_all()
1970
-         * @param boolean  $allow_blocking whether or not to allow related model objects
1971
-         *                                 to block (prevent) this deletion
1972
-         */
1973
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1974
-        //some MySQL databases may be running safe mode, which may restrict
1975
-        //deletion if there is no KEY column used in the WHERE statement of a deletion.
1976
-        //to get around this, we first do a SELECT, get all the IDs, and then run another query
1977
-        //to delete them
1978
-        $items_for_deletion = $this->_get_all_wpdb_results($query_params);
1979
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1980
-        $deletion_where_query_part = $this->_build_query_part_for_deleting_from_columns_and_values(
1981
-            $columns_and_ids_for_deleting
1982
-        );
1983
-        /**
1984
-         * Allows client code to act on the items being deleted before the query is actually executed.
1985
-         *
1986
-         * @param EEM_Base $this  The model instance being acted on.
1987
-         * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
1988
-         * @param bool     $allow_blocking @see param description in method phpdoc block.
1989
-         * @param array $columns_and_ids_for_deleting       An array indicating what entities will get removed as
1990
-         *                                                  derived from the incoming query parameters.
1991
-         *                                                  @see details on the structure of this array in the phpdocs
1992
-         *                                                  for the `_get_ids_for_delete_method`
1993
-         *
1994
-         */
1995
-        do_action('AHEE__EEM_Base__delete__before_query',
1996
-            $this,
1997
-            $query_params,
1998
-            $allow_blocking,
1999
-            $columns_and_ids_for_deleting
2000
-        );
2001
-        if ($deletion_where_query_part) {
2002
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2003
-            $table_aliases = array_keys($this->_tables);
2004
-            $SQL = "DELETE "
2005
-                   . implode(", ", $table_aliases)
2006
-                   . " FROM "
2007
-                   . $model_query_info->get_full_join_sql()
2008
-                   . " WHERE "
2009
-                   . $deletion_where_query_part;
2010
-            $rows_deleted = $this->_do_wpdb_query('query', array($SQL));
2011
-        } else {
2012
-            $rows_deleted = 0;
2013
-        }
2014
-
2015
-        //Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2016
-        //there was no error with the delete query.
2017
-        if ($this->has_primary_key_field()
2018
-            && $rows_deleted !== false
2019
-            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2020
-        ) {
2021
-            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2022
-            foreach ($ids_for_removal as $id) {
2023
-                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2024
-                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2025
-                }
2026
-            }
2027
-
2028
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2029
-            //`EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2030
-            //unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2031
-            // (although it is possible).
2032
-            //Note this can be skipped by using the provided filter and returning false.
2033
-            if (apply_filters(
2034
-                'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2035
-                ! $this instanceof EEM_Extra_Meta,
2036
-                $this
2037
-            )) {
2038
-                EEM_Extra_Meta::instance()->delete_permanently(array(
2039
-                    0 => array(
2040
-                        'EXM_type' => $this->get_this_model_name(),
2041
-                        'OBJ_ID'   => array(
2042
-                            'IN',
2043
-                            $ids_for_removal
2044
-                        )
2045
-                    )
2046
-                ));
2047
-            }
2048
-        }
2049
-
2050
-        /**
2051
-         * Action called just after performing a real deletion query. Although at this point the
2052
-         * items should have been deleted
2053
-         *
2054
-         * @param EEM_Base $model
2055
-         * @param array    $query_params @see EEM_Base::get_all()
2056
-         * @param int      $rows_deleted
2057
-         */
2058
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2059
-        return $rows_deleted;//how many supposedly got deleted
2060
-    }
2061
-
2062
-
2063
-
2064
-    /**
2065
-     * Checks all the relations that throw error messages when there are blocking related objects
2066
-     * for related model objects. If there are any related model objects on those relations,
2067
-     * adds an EE_Error, and return true
2068
-     *
2069
-     * @param EE_Base_Class|int $this_model_obj_or_id
2070
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2071
-     *                                                 should be ignored when determining whether there are related
2072
-     *                                                 model objects which block this model object's deletion. Useful
2073
-     *                                                 if you know A is related to B and are considering deleting A,
2074
-     *                                                 but want to see if A has any other objects blocking its deletion
2075
-     *                                                 before removing the relation between A and B
2076
-     * @return boolean
2077
-     * @throws EE_Error
2078
-     */
2079
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2080
-    {
2081
-        //first, if $ignore_this_model_obj was supplied, get its model
2082
-        if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2083
-            $ignored_model = $ignore_this_model_obj->get_model();
2084
-        } else {
2085
-            $ignored_model = null;
2086
-        }
2087
-        //now check all the relations of $this_model_obj_or_id and see if there
2088
-        //are any related model objects blocking it?
2089
-        $is_blocked = false;
2090
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2091
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2092
-                //if $ignore_this_model_obj was supplied, then for the query
2093
-                //on that model needs to be told to ignore $ignore_this_model_obj
2094
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2095
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, array(
2096
-                        array(
2097
-                            $ignored_model->get_primary_key_field()->get_name() => array(
2098
-                                '!=',
2099
-                                $ignore_this_model_obj->ID(),
2100
-                            ),
2101
-                        ),
2102
-                    ));
2103
-                } else {
2104
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2105
-                }
2106
-                if ($related_model_objects) {
2107
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2108
-                    $is_blocked = true;
2109
-                }
2110
-            }
2111
-        }
2112
-        return $is_blocked;
2113
-    }
2114
-
2115
-
2116
-    /**
2117
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2118
-     * @param array $row_results_for_deleting
2119
-     * @param bool  $allow_blocking
2120
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2121
-     *                 model DOES have a primary_key_field, then the array will be a simple single dimension array where
2122
-     *                 the key is the fully qualified primary key column and the value is an array of ids that will be
2123
-     *                 deleted. Example:
2124
-     *                      array('Event.EVT_ID' => array( 1,2,3))
2125
-     *                 If the model DOES NOT have a primary_key_field, then the array will be a two dimensional array
2126
-     *                 where each element is a group of columns and values that get deleted. Example:
2127
-     *                      array(
2128
-     *                          0 => array(
2129
-     *                              'Term_Relationship.object_id' => 1
2130
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2131
-     *                          ),
2132
-     *                          1 => array(
2133
-     *                              'Term_Relationship.object_id' => 1
2134
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2135
-     *                          )
2136
-     *                      )
2137
-     * @throws EE_Error
2138
-     */
2139
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2140
-    {
2141
-        $ids_to_delete_indexed_by_column = array();
2142
-        if ($this->has_primary_key_field()) {
2143
-            $primary_table = $this->_get_main_table();
2144
-            $primary_table_pk_field = $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2145
-            $other_tables = $this->_get_other_tables();
2146
-            $ids_to_delete_indexed_by_column = $query = array();
2147
-            foreach ($row_results_for_deleting as $item_to_delete) {
2148
-                //before we mark this item for deletion,
2149
-                //make sure there's no related entities blocking its deletion (if we're checking)
2150
-                if (
2151
-                    $allow_blocking
2152
-                    && $this->delete_is_blocked_by_related_models(
2153
-                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2154
-                    )
2155
-                ) {
2156
-                    continue;
2157
-                }
2158
-                //primary table deletes
2159
-                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2160
-                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2161
-                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2162
-                }
2163
-            }
2164
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2165
-            $fields = $this->get_combined_primary_key_fields();
2166
-            foreach ($row_results_for_deleting as $item_to_delete) {
2167
-                $ids_to_delete_indexed_by_column_for_row = array();
2168
-                foreach ($fields as $cpk_field) {
2169
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2170
-                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2171
-                            $item_to_delete[$cpk_field->get_qualified_column()];
2172
-                    }
2173
-                }
2174
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2175
-            }
2176
-        } else {
2177
-            //so there's no primary key and no combined key...
2178
-            //sorry, can't help you
2179
-            throw new EE_Error(
2180
-                sprintf(
2181
-                    __(
2182
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2183
-                        "event_espresso"
2184
-                    ), get_class($this)
2185
-                )
2186
-            );
2187
-        }
2188
-        return $ids_to_delete_indexed_by_column;
2189
-    }
2190
-
2191
-
2192
-    /**
2193
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2194
-     * the corresponding query_part for the query performing the delete.
2195
-     *
2196
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2197
-     * @return string
2198
-     * @throws EE_Error
2199
-     */
2200
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column) {
2201
-        $query_part = '';
2202
-        if (empty($ids_to_delete_indexed_by_column)) {
2203
-            return $query_part;
2204
-        } elseif ($this->has_primary_key_field()) {
2205
-            $query = array();
2206
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2207
-                //make sure we have unique $ids
2208
-                $ids = array_unique($ids);
2209
-                $query[] = $column . ' IN(' . implode(',', $ids) . ')';
2210
-            }
2211
-            $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2212
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2213
-            $ways_to_identify_a_row = array();
2214
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2215
-                $values_for_each_combined_primary_key_for_a_row = array();
2216
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2217
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2218
-                }
2219
-                $ways_to_identify_a_row[] = '('
2220
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2221
-                                            . ')';
2222
-            }
2223
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2224
-        }
2225
-        return $query_part;
2226
-    }
2227
-
2228
-
2229
-
2230
-    /**
2231
-     * Gets the model field by the fully qualified name
2232
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2233
-     * @return EE_Model_Field_Base
2234
-     */
2235
-    public function get_field_by_column($qualified_column_name)
2236
-    {
2237
-       foreach($this->field_settings(true) as $field_name => $field_obj){
2238
-           if($field_obj->get_qualified_column() === $qualified_column_name){
2239
-               return $field_obj;
2240
-           }
2241
-       }
2242
-        throw new EE_Error(
2243
-            sprintf(
2244
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2245
-                $this->get_this_model_name(),
2246
-                $qualified_column_name
2247
-            )
2248
-        );
2249
-    }
2250
-
2251
-
2252
-
2253
-    /**
2254
-     * Count all the rows that match criteria expressed in $query_params (an array just like arg to EEM_Base::get_all).
2255
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2256
-     * column
2257
-     *
2258
-     * @param array  $query_params   like EEM_Base::get_all's
2259
-     * @param string $field_to_count field on model to count by (not column name)
2260
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2261
-     *                               that by the setting $distinct to TRUE;
2262
-     * @return int
2263
-     * @throws EE_Error
2264
-     */
2265
-    public function count($query_params = array(), $field_to_count = null, $distinct = false)
2266
-    {
2267
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2268
-        if ($field_to_count) {
2269
-            $field_obj = $this->field_settings_for($field_to_count);
2270
-            $column_to_count = $field_obj->get_qualified_column();
2271
-        } elseif ($this->has_primary_key_field()) {
2272
-            $pk_field_obj = $this->get_primary_key_field();
2273
-            $column_to_count = $pk_field_obj->get_qualified_column();
2274
-        } else {
2275
-            //there's no primary key
2276
-            //if we're counting distinct items, and there's no primary key,
2277
-            //we need to list out the columns for distinction;
2278
-            //otherwise we can just use star
2279
-            if ($distinct) {
2280
-                $columns_to_use = array();
2281
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2282
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2283
-                }
2284
-                $column_to_count = implode(',', $columns_to_use);
2285
-            } else {
2286
-                $column_to_count = '*';
2287
-            }
2288
-        }
2289
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2290
-        $SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2291
-        return (int)$this->_do_wpdb_query('get_var', array($SQL));
2292
-    }
2293
-
2294
-
2295
-
2296
-    /**
2297
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2298
-     *
2299
-     * @param array  $query_params like EEM_Base::get_all
2300
-     * @param string $field_to_sum name of field (array key in $_fields array)
2301
-     * @return float
2302
-     * @throws EE_Error
2303
-     */
2304
-    public function sum($query_params, $field_to_sum = null)
2305
-    {
2306
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2307
-        if ($field_to_sum) {
2308
-            $field_obj = $this->field_settings_for($field_to_sum);
2309
-        } else {
2310
-            $field_obj = $this->get_primary_key_field();
2311
-        }
2312
-        $column_to_count = $field_obj->get_qualified_column();
2313
-        $SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2314
-        $return_value = $this->_do_wpdb_query('get_var', array($SQL));
2315
-        $data_type = $field_obj->get_wpdb_data_type();
2316
-        if ($data_type === '%d' || $data_type === '%s') {
2317
-            return (float)$return_value;
2318
-        }
2319
-        //must be %f
2320
-        return (float)$return_value;
2321
-    }
2322
-
2323
-
2324
-
2325
-    /**
2326
-     * Just calls the specified method on $wpdb with the given arguments
2327
-     * Consolidates a little extra error handling code
2328
-     *
2329
-     * @param string $wpdb_method
2330
-     * @param array  $arguments_to_provide
2331
-     * @throws EE_Error
2332
-     * @global wpdb  $wpdb
2333
-     * @return mixed
2334
-     */
2335
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2336
-    {
2337
-        //if we're in maintenance mode level 2, DON'T run any queries
2338
-        //because level 2 indicates the database needs updating and
2339
-        //is probably out of sync with the code
2340
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2341
-            throw new EE_Error(sprintf(__("Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2342
-                "event_espresso")));
2343
-        }
2344
-        /** @type WPDB $wpdb */
2345
-        global $wpdb;
2346
-        if (! method_exists($wpdb, $wpdb_method)) {
2347
-            throw new EE_Error(sprintf(__('There is no method named "%s" on Wordpress\' $wpdb object',
2348
-                'event_espresso'), $wpdb_method));
2349
-        }
2350
-        if (WP_DEBUG) {
2351
-            $old_show_errors_value = $wpdb->show_errors;
2352
-            $wpdb->show_errors(false);
2353
-        }
2354
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2355
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2356
-        if (WP_DEBUG) {
2357
-            $wpdb->show_errors($old_show_errors_value);
2358
-            if (! empty($wpdb->last_error)) {
2359
-                throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2360
-            }
2361
-            if ($result === false) {
2362
-                throw new EE_Error(sprintf(__('WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2363
-                    'event_espresso'), $wpdb_method, var_export($arguments_to_provide, true)));
2364
-            }
2365
-        } elseif ($result === false) {
2366
-            EE_Error::add_error(
2367
-                sprintf(
2368
-                    __('A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2369
-                        'event_espresso'),
2370
-                    $wpdb_method,
2371
-                    var_export($arguments_to_provide, true),
2372
-                    $wpdb->last_error
2373
-                ),
2374
-                __FILE__,
2375
-                __FUNCTION__,
2376
-                __LINE__
2377
-            );
2378
-        }
2379
-        return $result;
2380
-    }
2381
-
2382
-
2383
-
2384
-    /**
2385
-     * Attempts to run the indicated WPDB method with the provided arguments,
2386
-     * and if there's an error tries to verify the DB is correct. Uses
2387
-     * the static property EEM_Base::$_db_verification_level to determine whether
2388
-     * we should try to fix the EE core db, the addons, or just give up
2389
-     *
2390
-     * @param string $wpdb_method
2391
-     * @param array  $arguments_to_provide
2392
-     * @return mixed
2393
-     */
2394
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2395
-    {
2396
-        /** @type WPDB $wpdb */
2397
-        global $wpdb;
2398
-        $wpdb->last_error = null;
2399
-        $result = call_user_func_array(array($wpdb, $wpdb_method), $arguments_to_provide);
2400
-        // was there an error running the query? but we don't care on new activations
2401
-        // (we're going to setup the DB anyway on new activations)
2402
-        if (($result === false || ! empty($wpdb->last_error))
2403
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2404
-        ) {
2405
-            switch (EEM_Base::$_db_verification_level) {
2406
-                case EEM_Base::db_verified_none :
2407
-                    // let's double-check core's DB
2408
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2409
-                    break;
2410
-                case EEM_Base::db_verified_core :
2411
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2412
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2413
-                    break;
2414
-                case EEM_Base::db_verified_addons :
2415
-                    // ummmm... you in trouble
2416
-                    return $result;
2417
-                    break;
2418
-            }
2419
-            if (! empty($error_message)) {
2420
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2421
-                trigger_error($error_message);
2422
-            }
2423
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2424
-        }
2425
-        return $result;
2426
-    }
2427
-
2428
-
2429
-
2430
-    /**
2431
-     * Verifies the EE core database is up-to-date and records that we've done it on
2432
-     * EEM_Base::$_db_verification_level
2433
-     *
2434
-     * @param string $wpdb_method
2435
-     * @param array  $arguments_to_provide
2436
-     * @return string
2437
-     */
2438
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2439
-    {
2440
-        /** @type WPDB $wpdb */
2441
-        global $wpdb;
2442
-        //ok remember that we've already attempted fixing the core db, in case the problem persists
2443
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2444
-        $error_message = sprintf(
2445
-            __('WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2446
-                'event_espresso'),
2447
-            $wpdb->last_error,
2448
-            $wpdb_method,
2449
-            wp_json_encode($arguments_to_provide)
2450
-        );
2451
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2452
-        return $error_message;
2453
-    }
2454
-
2455
-
2456
-
2457
-    /**
2458
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2459
-     * EEM_Base::$_db_verification_level
2460
-     *
2461
-     * @param $wpdb_method
2462
-     * @param $arguments_to_provide
2463
-     * @return string
2464
-     */
2465
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2466
-    {
2467
-        /** @type WPDB $wpdb */
2468
-        global $wpdb;
2469
-        //ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2470
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2471
-        $error_message = sprintf(
2472
-            __('WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2473
-                'event_espresso'),
2474
-            $wpdb->last_error,
2475
-            $wpdb_method,
2476
-            wp_json_encode($arguments_to_provide)
2477
-        );
2478
-        EE_System::instance()->initialize_addons();
2479
-        return $error_message;
2480
-    }
2481
-
2482
-
2483
-
2484
-    /**
2485
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2486
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2487
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2488
-     * ..."
2489
-     *
2490
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2491
-     * @return string
2492
-     */
2493
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2494
-    {
2495
-        return " FROM " . $model_query_info->get_full_join_sql() .
2496
-               $model_query_info->get_where_sql() .
2497
-               $model_query_info->get_group_by_sql() .
2498
-               $model_query_info->get_having_sql() .
2499
-               $model_query_info->get_order_by_sql() .
2500
-               $model_query_info->get_limit_sql();
2501
-    }
2502
-
2503
-
2504
-
2505
-    /**
2506
-     * Set to easily debug the next X queries ran from this model.
2507
-     *
2508
-     * @param int $count
2509
-     */
2510
-    public function show_next_x_db_queries($count = 1)
2511
-    {
2512
-        $this->_show_next_x_db_queries = $count;
2513
-    }
2514
-
2515
-
2516
-
2517
-    /**
2518
-     * @param $sql_query
2519
-     */
2520
-    public function show_db_query_if_previously_requested($sql_query)
2521
-    {
2522
-        if ($this->_show_next_x_db_queries > 0) {
2523
-            echo $sql_query;
2524
-            $this->_show_next_x_db_queries--;
2525
-        }
2526
-    }
2527
-
2528
-
2529
-
2530
-    /**
2531
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2532
-     * There are the 3 cases:
2533
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2534
-     * $otherModelObject has no ID, it is first saved.
2535
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2536
-     * has no ID, it is first saved.
2537
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2538
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2539
-     * join table
2540
-     *
2541
-     * @param        EE_Base_Class                     /int $thisModelObject
2542
-     * @param        EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2543
-     * @param string $relationName                     , key in EEM_Base::_relations
2544
-     *                                                 an attendee to a group, you also want to specify which role they
2545
-     *                                                 will have in that group. So you would use this parameter to
2546
-     *                                                 specify array('role-column-name'=>'role-id')
2547
-     * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2548
-     *                                                 to for relation to methods that allow you to further specify
2549
-     *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2550
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2551
-     *                                                 because these will be inserted in any new rows created as well.
2552
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2553
-     * @throws EE_Error
2554
-     */
2555
-    public function add_relationship_to(
2556
-        $id_or_obj,
2557
-        $other_model_id_or_obj,
2558
-        $relationName,
2559
-        $extra_join_model_fields_n_values = array()
2560
-    ) {
2561
-        $relation_obj = $this->related_settings_for($relationName);
2562
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2563
-    }
2564
-
2565
-
2566
-
2567
-    /**
2568
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2569
-     * There are the 3 cases:
2570
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2571
-     * error
2572
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2573
-     * an error
2574
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2575
-     *
2576
-     * @param        EE_Base_Class /int $id_or_obj
2577
-     * @param        EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2578
-     * @param string $relationName key in EEM_Base::_relations
2579
-     * @return boolean of success
2580
-     * @throws EE_Error
2581
-     * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2582
-     *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2583
-     *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2584
-     *                             because these will be inserted in any new rows created as well.
2585
-     */
2586
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = array())
2587
-    {
2588
-        $relation_obj = $this->related_settings_for($relationName);
2589
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2590
-    }
2591
-
2592
-
2593
-
2594
-    /**
2595
-     * @param mixed           $id_or_obj
2596
-     * @param string          $relationName
2597
-     * @param array           $where_query_params
2598
-     * @param EE_Base_Class[] objects to which relations were removed
2599
-     * @return \EE_Base_Class[]
2600
-     * @throws EE_Error
2601
-     */
2602
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = array())
2603
-    {
2604
-        $relation_obj = $this->related_settings_for($relationName);
2605
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2606
-    }
2607
-
2608
-
2609
-
2610
-    /**
2611
-     * Gets all the related items of the specified $model_name, using $query_params.
2612
-     * Note: by default, we remove the "default query params"
2613
-     * because we want to get even deleted items etc.
2614
-     *
2615
-     * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2616
-     * @param string $model_name   like 'Event', 'Registration', etc. always singular
2617
-     * @param array  $query_params like EEM_Base::get_all
2618
-     * @return EE_Base_Class[]
2619
-     * @throws EE_Error
2620
-     */
2621
-    public function get_all_related($id_or_obj, $model_name, $query_params = null)
2622
-    {
2623
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2624
-        $relation_settings = $this->related_settings_for($model_name);
2625
-        return $relation_settings->get_all_related($model_obj, $query_params);
2626
-    }
2627
-
2628
-
2629
-
2630
-    /**
2631
-     * Deletes all the model objects across the relation indicated by $model_name
2632
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2633
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2634
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2635
-     *
2636
-     * @param EE_Base_Class|int|string $id_or_obj
2637
-     * @param string                   $model_name
2638
-     * @param array                    $query_params
2639
-     * @return int how many deleted
2640
-     * @throws EE_Error
2641
-     */
2642
-    public function delete_related($id_or_obj, $model_name, $query_params = array())
2643
-    {
2644
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2645
-        $relation_settings = $this->related_settings_for($model_name);
2646
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2647
-    }
2648
-
2649
-
2650
-
2651
-    /**
2652
-     * Hard deletes all the model objects across the relation indicated by $model_name
2653
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2654
-     * the model objects can't be hard deleted because of blocking related model objects,
2655
-     * just does a soft-delete on them instead.
2656
-     *
2657
-     * @param EE_Base_Class|int|string $id_or_obj
2658
-     * @param string                   $model_name
2659
-     * @param array                    $query_params
2660
-     * @return int how many deleted
2661
-     * @throws EE_Error
2662
-     */
2663
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = array())
2664
-    {
2665
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2666
-        $relation_settings = $this->related_settings_for($model_name);
2667
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2668
-    }
2669
-
2670
-
2671
-
2672
-    /**
2673
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2674
-     * unless otherwise specified in the $query_params
2675
-     *
2676
-     * @param        int             /EE_Base_Class $id_or_obj
2677
-     * @param string $model_name     like 'Event', or 'Registration'
2678
-     * @param array  $query_params   like EEM_Base::get_all's
2679
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2680
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2681
-     *                               that by the setting $distinct to TRUE;
2682
-     * @return int
2683
-     * @throws EE_Error
2684
-     */
2685
-    public function count_related(
2686
-        $id_or_obj,
2687
-        $model_name,
2688
-        $query_params = array(),
2689
-        $field_to_count = null,
2690
-        $distinct = false
2691
-    ) {
2692
-        $related_model = $this->get_related_model_obj($model_name);
2693
-        //we're just going to use the query params on the related model's normal get_all query,
2694
-        //except add a condition to say to match the current mod
2695
-        if (! isset($query_params['default_where_conditions'])) {
2696
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2697
-        }
2698
-        $this_model_name = $this->get_this_model_name();
2699
-        $this_pk_field_name = $this->get_primary_key_field()->get_name();
2700
-        $query_params[0][$this_model_name . "." . $this_pk_field_name] = $id_or_obj;
2701
-        return $related_model->count($query_params, $field_to_count, $distinct);
2702
-    }
2703
-
2704
-
2705
-
2706
-    /**
2707
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2708
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2709
-     *
2710
-     * @param        int           /EE_Base_Class $id_or_obj
2711
-     * @param string $model_name   like 'Event', or 'Registration'
2712
-     * @param array  $query_params like EEM_Base::get_all's
2713
-     * @param string $field_to_sum name of field to count by. By default, uses primary key
2714
-     * @return float
2715
-     * @throws EE_Error
2716
-     */
2717
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2718
-    {
2719
-        $related_model = $this->get_related_model_obj($model_name);
2720
-        if (! is_array($query_params)) {
2721
-            EE_Error::doing_it_wrong('EEM_Base::sum_related',
2722
-                sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2723
-                    gettype($query_params)), '4.6.0');
2724
-            $query_params = array();
2725
-        }
2726
-        //we're just going to use the query params on the related model's normal get_all query,
2727
-        //except add a condition to say to match the current mod
2728
-        if (! isset($query_params['default_where_conditions'])) {
2729
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2730
-        }
2731
-        $this_model_name = $this->get_this_model_name();
2732
-        $this_pk_field_name = $this->get_primary_key_field()->get_name();
2733
-        $query_params[0][$this_model_name . "." . $this_pk_field_name] = $id_or_obj;
2734
-        return $related_model->sum($query_params, $field_to_sum);
2735
-    }
2736
-
2737
-
2738
-
2739
-    /**
2740
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2741
-     * $modelObject
2742
-     *
2743
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2744
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2745
-     * @param array               $query_params     like EEM_Base::get_all's
2746
-     * @return EE_Base_Class
2747
-     * @throws EE_Error
2748
-     */
2749
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2750
-    {
2751
-        $query_params['limit'] = 1;
2752
-        $results = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2753
-        if ($results) {
2754
-            return array_shift($results);
2755
-        }
2756
-        return null;
2757
-    }
2758
-
2759
-
2760
-
2761
-    /**
2762
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2763
-     *
2764
-     * @return string
2765
-     */
2766
-    public function get_this_model_name()
2767
-    {
2768
-        return str_replace("EEM_", "", get_class($this));
2769
-    }
2770
-
2771
-
2772
-
2773
-    /**
2774
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2775
-     *
2776
-     * @return EE_Any_Foreign_Model_Name_Field
2777
-     * @throws EE_Error
2778
-     */
2779
-    public function get_field_containing_related_model_name()
2780
-    {
2781
-        foreach ($this->field_settings(true) as $field) {
2782
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2783
-                $field_with_model_name = $field;
2784
-            }
2785
-        }
2786
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2787
-            throw new EE_Error(sprintf(__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2788
-                $this->get_this_model_name()));
2789
-        }
2790
-        return $field_with_model_name;
2791
-    }
2792
-
2793
-
2794
-
2795
-    /**
2796
-     * Inserts a new entry into the database, for each table.
2797
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2798
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2799
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2800
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2801
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2802
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2803
-     *
2804
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2805
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2806
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2807
-     *                              of EEM_Base)
2808
-     * @return int new primary key on main table that got inserted
2809
-     * @throws EE_Error
2810
-     */
2811
-    public function insert($field_n_values)
2812
-    {
2813
-        /**
2814
-         * Filters the fields and their values before inserting an item using the models
2815
-         *
2816
-         * @param array    $fields_n_values keys are the fields and values are their new values
2817
-         * @param EEM_Base $model           the model used
2818
-         */
2819
-        $field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2820
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2821
-            $main_table = $this->_get_main_table();
2822
-            $new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2823
-            if ($new_id !== false) {
2824
-                foreach ($this->_get_other_tables() as $other_table) {
2825
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2826
-                }
2827
-            }
2828
-            /**
2829
-             * Done just after attempting to insert a new model object
2830
-             *
2831
-             * @param EEM_Base   $model           used
2832
-             * @param array      $fields_n_values fields and their values
2833
-             * @param int|string the              ID of the newly-inserted model object
2834
-             */
2835
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2836
-            return $new_id;
2837
-        }
2838
-        return false;
2839
-    }
2840
-
2841
-
2842
-
2843
-    /**
2844
-     * Checks that the result would satisfy the unique indexes on this model
2845
-     *
2846
-     * @param array  $field_n_values
2847
-     * @param string $action
2848
-     * @return boolean
2849
-     * @throws EE_Error
2850
-     */
2851
-    protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2852
-    {
2853
-        foreach ($this->unique_indexes() as $index_name => $index) {
2854
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2855
-            if ($this->exists(array($uniqueness_where_params))) {
2856
-                EE_Error::add_error(
2857
-                    sprintf(
2858
-                        __(
2859
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2860
-                            "event_espresso"
2861
-                        ),
2862
-                        $action,
2863
-                        $this->_get_class_name(),
2864
-                        $index_name,
2865
-                        implode(",", $index->field_names()),
2866
-                        http_build_query($uniqueness_where_params)
2867
-                    ),
2868
-                    __FILE__,
2869
-                    __FUNCTION__,
2870
-                    __LINE__
2871
-                );
2872
-                return false;
2873
-            }
2874
-        }
2875
-        return true;
2876
-    }
2877
-
2878
-
2879
-
2880
-    /**
2881
-     * Checks the database for an item that conflicts (ie, if this item were
2882
-     * saved to the DB would break some uniqueness requirement, like a primary key
2883
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
2884
-     * can be either an EE_Base_Class or an array of fields n values
2885
-     *
2886
-     * @param EE_Base_Class|array $obj_or_fields_array
2887
-     * @param boolean             $include_primary_key whether to use the model object's primary key
2888
-     *                                                 when looking for conflicts
2889
-     *                                                 (ie, if false, we ignore the model object's primary key
2890
-     *                                                 when finding "conflicts". If true, it's also considered).
2891
-     *                                                 Only works for INT primary key,
2892
-     *                                                 STRING primary keys cannot be ignored
2893
-     * @throws EE_Error
2894
-     * @return EE_Base_Class|array
2895
-     */
2896
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2897
-    {
2898
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
2899
-            $fields_n_values = $obj_or_fields_array->model_field_array();
2900
-        } elseif (is_array($obj_or_fields_array)) {
2901
-            $fields_n_values = $obj_or_fields_array;
2902
-        } else {
2903
-            throw new EE_Error(
2904
-                sprintf(
2905
-                    __(
2906
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2907
-                        "event_espresso"
2908
-                    ),
2909
-                    get_class($this),
2910
-                    $obj_or_fields_array
2911
-                )
2912
-            );
2913
-        }
2914
-        $query_params = array();
2915
-        if ($this->has_primary_key_field()
2916
-            && ($include_primary_key
2917
-                || $this->get_primary_key_field()
2918
-                   instanceof
2919
-                   EE_Primary_Key_String_Field)
2920
-            && isset($fields_n_values[$this->primary_key_name()])
2921
-        ) {
2922
-            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
2923
-        }
2924
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2925
-            $uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2926
-            $query_params[0]['OR']['AND*' . $unique_index_name] = $uniqueness_where_params;
2927
-        }
2928
-        //if there is nothing to base this search on, then we shouldn't find anything
2929
-        if (empty($query_params)) {
2930
-            return array();
2931
-        }
2932
-        return $this->get_one($query_params);
2933
-    }
2934
-
2935
-
2936
-
2937
-    /**
2938
-     * Like count, but is optimized and returns a boolean instead of an int
2939
-     *
2940
-     * @param array $query_params
2941
-     * @return boolean
2942
-     * @throws EE_Error
2943
-     */
2944
-    public function exists($query_params)
2945
-    {
2946
-        $query_params['limit'] = 1;
2947
-        return $this->count($query_params) > 0;
2948
-    }
2949
-
2950
-
2951
-
2952
-    /**
2953
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
2954
-     *
2955
-     * @param int|string $id
2956
-     * @return boolean
2957
-     * @throws EE_Error
2958
-     */
2959
-    public function exists_by_ID($id)
2960
-    {
2961
-        return $this->exists(
2962
-            array(
2963
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
2964
-                array(
2965
-                    $this->primary_key_name() => $id,
2966
-                ),
2967
-            )
2968
-        );
2969
-    }
2970
-
2971
-
2972
-
2973
-    /**
2974
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
2975
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
2976
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
2977
-     * on the main table)
2978
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
2979
-     * cases where we want to call it directly rather than via insert().
2980
-     *
2981
-     * @access   protected
2982
-     * @param EE_Table_Base $table
2983
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
2984
-     *                                       float
2985
-     * @param int           $new_id          for now we assume only int keys
2986
-     * @throws EE_Error
2987
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
2988
-     * @return int ID of new row inserted, or FALSE on failure
2989
-     */
2990
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
2991
-    {
2992
-        global $wpdb;
2993
-        $insertion_col_n_values = array();
2994
-        $format_for_insertion = array();
2995
-        $fields_on_table = $this->_get_fields_for_table($table->get_table_alias());
2996
-        foreach ($fields_on_table as $field_name => $field_obj) {
2997
-            //check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
2998
-            if ($field_obj->is_auto_increment()) {
2999
-                continue;
3000
-            }
3001
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3002
-            //if the value we want to assign it to is NULL, just don't mention it for the insertion
3003
-            if ($prepared_value !== null) {
3004
-                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3005
-                $format_for_insertion[] = $field_obj->get_wpdb_data_type();
3006
-            }
3007
-        }
3008
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3009
-            //its not the main table, so we should have already saved the main table's PK which we just inserted
3010
-            //so add the fk to the main table as a column
3011
-            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3012
-            $format_for_insertion[] = '%d';//yes right now we're only allowing these foreign keys to be INTs
3013
-        }
3014
-        //insert the new entry
3015
-        $result = $this->_do_wpdb_query('insert',
3016
-            array($table->get_table_name(), $insertion_col_n_values, $format_for_insertion));
3017
-        if ($result === false) {
3018
-            return false;
3019
-        }
3020
-        //ok, now what do we return for the ID of the newly-inserted thing?
3021
-        if ($this->has_primary_key_field()) {
3022
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3023
-                return $wpdb->insert_id;
3024
-            }
3025
-            //it's not an auto-increment primary key, so
3026
-            //it must have been supplied
3027
-            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3028
-        }
3029
-        //we can't return a  primary key because there is none. instead return
3030
-        //a unique string indicating this model
3031
-        return $this->get_index_primary_key_string($fields_n_values);
3032
-    }
3033
-
3034
-
3035
-
3036
-    /**
3037
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3038
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3039
-     * and there is no default, we pass it along. WPDB will take care of it)
3040
-     *
3041
-     * @param EE_Model_Field_Base $field_obj
3042
-     * @param array               $fields_n_values
3043
-     * @return mixed string|int|float depending on what the table column will be expecting
3044
-     * @throws EE_Error
3045
-     */
3046
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3047
-    {
3048
-        //if this field doesn't allow nullable, don't allow it
3049
-        if (
3050
-            ! $field_obj->is_nullable()
3051
-            && (
3052
-                ! isset($fields_n_values[$field_obj->get_name()])
3053
-                || $fields_n_values[$field_obj->get_name()] === null
3054
-            )
3055
-        ) {
3056
-            $fields_n_values[$field_obj->get_name()] = $field_obj->get_default_value();
3057
-        }
3058
-        $unprepared_value = isset($fields_n_values[$field_obj->get_name()])
3059
-            ? $fields_n_values[$field_obj->get_name()]
3060
-            : null;
3061
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3062
-    }
3063
-
3064
-
3065
-
3066
-    /**
3067
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3068
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3069
-     * the field's prepare_for_set() method.
3070
-     *
3071
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3072
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3073
-     *                                   top of file)
3074
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3075
-     *                                   $value is a custom selection
3076
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3077
-     */
3078
-    private function _prepare_value_for_use_in_db($value, $field)
3079
-    {
3080
-        if ($field && $field instanceof EE_Model_Field_Base) {
3081
-            switch ($this->_values_already_prepared_by_model_object) {
3082
-                /** @noinspection PhpMissingBreakStatementInspection */
3083
-                case self::not_prepared_by_model_object:
3084
-                    $value = $field->prepare_for_set($value);
3085
-                //purposefully left out "return"
3086
-                case self::prepared_by_model_object:
3087
-                    /** @noinspection SuspiciousAssignmentsInspection */
3088
-                    $value = $field->prepare_for_use_in_db($value);
3089
-                case self::prepared_for_use_in_db:
3090
-                    //leave the value alone
3091
-            }
3092
-            return $value;
3093
-        }
3094
-        return $value;
3095
-    }
3096
-
3097
-
3098
-
3099
-    /**
3100
-     * Returns the main table on this model
3101
-     *
3102
-     * @return EE_Primary_Table
3103
-     * @throws EE_Error
3104
-     */
3105
-    protected function _get_main_table()
3106
-    {
3107
-        foreach ($this->_tables as $table) {
3108
-            if ($table instanceof EE_Primary_Table) {
3109
-                return $table;
3110
-            }
3111
-        }
3112
-        throw new EE_Error(sprintf(__('There are no main tables on %s. They should be added to _tables array in the constructor',
3113
-            'event_espresso'), get_class($this)));
3114
-    }
3115
-
3116
-
3117
-
3118
-    /**
3119
-     * table
3120
-     * returns EE_Primary_Table table name
3121
-     *
3122
-     * @return string
3123
-     * @throws EE_Error
3124
-     */
3125
-    public function table()
3126
-    {
3127
-        return $this->_get_main_table()->get_table_name();
3128
-    }
3129
-
3130
-
3131
-
3132
-    /**
3133
-     * table
3134
-     * returns first EE_Secondary_Table table name
3135
-     *
3136
-     * @return string
3137
-     */
3138
-    public function second_table()
3139
-    {
3140
-        // grab second table from tables array
3141
-        $second_table = end($this->_tables);
3142
-        return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3143
-    }
3144
-
3145
-
3146
-
3147
-    /**
3148
-     * get_table_obj_by_alias
3149
-     * returns table name given it's alias
3150
-     *
3151
-     * @param string $table_alias
3152
-     * @return EE_Primary_Table | EE_Secondary_Table
3153
-     */
3154
-    public function get_table_obj_by_alias($table_alias = '')
3155
-    {
3156
-        return isset($this->_tables[$table_alias]) ? $this->_tables[$table_alias] : null;
3157
-    }
3158
-
3159
-
3160
-
3161
-    /**
3162
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3163
-     *
3164
-     * @return EE_Secondary_Table[]
3165
-     */
3166
-    protected function _get_other_tables()
3167
-    {
3168
-        $other_tables = array();
3169
-        foreach ($this->_tables as $table_alias => $table) {
3170
-            if ($table instanceof EE_Secondary_Table) {
3171
-                $other_tables[$table_alias] = $table;
3172
-            }
3173
-        }
3174
-        return $other_tables;
3175
-    }
3176
-
3177
-
3178
-
3179
-    /**
3180
-     * Finds all the fields that correspond to the given table
3181
-     *
3182
-     * @param string $table_alias , array key in EEM_Base::_tables
3183
-     * @return EE_Model_Field_Base[]
3184
-     */
3185
-    public function _get_fields_for_table($table_alias)
3186
-    {
3187
-        return $this->_fields[$table_alias];
3188
-    }
3189
-
3190
-
3191
-
3192
-    /**
3193
-     * Recurses through all the where parameters, and finds all the related models we'll need
3194
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3195
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3196
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3197
-     * related Registration, Transaction, and Payment models.
3198
-     *
3199
-     * @param array $query_params like EEM_Base::get_all's $query_parameters['where']
3200
-     * @return EE_Model_Query_Info_Carrier
3201
-     * @throws EE_Error
3202
-     */
3203
-    public function _extract_related_models_from_query($query_params)
3204
-    {
3205
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3206
-        if (array_key_exists(0, $query_params)) {
3207
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3208
-        }
3209
-        if (array_key_exists('group_by', $query_params)) {
3210
-            if (is_array($query_params['group_by'])) {
3211
-                $this->_extract_related_models_from_sub_params_array_values(
3212
-                    $query_params['group_by'],
3213
-                    $query_info_carrier,
3214
-                    'group_by'
3215
-                );
3216
-            } elseif (! empty ($query_params['group_by'])) {
3217
-                $this->_extract_related_model_info_from_query_param(
3218
-                    $query_params['group_by'],
3219
-                    $query_info_carrier,
3220
-                    'group_by'
3221
-                );
3222
-            }
3223
-        }
3224
-        if (array_key_exists('having', $query_params)) {
3225
-            $this->_extract_related_models_from_sub_params_array_keys(
3226
-                $query_params[0],
3227
-                $query_info_carrier,
3228
-                'having'
3229
-            );
3230
-        }
3231
-        if (array_key_exists('order_by', $query_params)) {
3232
-            if (is_array($query_params['order_by'])) {
3233
-                $this->_extract_related_models_from_sub_params_array_keys(
3234
-                    $query_params['order_by'],
3235
-                    $query_info_carrier,
3236
-                    'order_by'
3237
-                );
3238
-            } elseif (! empty($query_params['order_by'])) {
3239
-                $this->_extract_related_model_info_from_query_param(
3240
-                    $query_params['order_by'],
3241
-                    $query_info_carrier,
3242
-                    'order_by'
3243
-                );
3244
-            }
3245
-        }
3246
-        if (array_key_exists('force_join', $query_params)) {
3247
-            $this->_extract_related_models_from_sub_params_array_values(
3248
-                $query_params['force_join'],
3249
-                $query_info_carrier,
3250
-                'force_join'
3251
-            );
3252
-        }
3253
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3254
-        return $query_info_carrier;
3255
-    }
3256
-
3257
-
3258
-
3259
-    /**
3260
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3261
-     *
3262
-     * @param array                       $sub_query_params like EEM_Base::get_all's $query_params[0] or
3263
-     *                                                      $query_params['having']
3264
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3265
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3266
-     * @throws EE_Error
3267
-     * @return \EE_Model_Query_Info_Carrier
3268
-     */
3269
-    private function _extract_related_models_from_sub_params_array_keys(
3270
-        $sub_query_params,
3271
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3272
-        $query_param_type
3273
-    ) {
3274
-        if (! empty($sub_query_params)) {
3275
-            $sub_query_params = (array)$sub_query_params;
3276
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3277
-                //$param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3278
-                $this->_extract_related_model_info_from_query_param($param, $model_query_info_carrier,
3279
-                    $query_param_type);
3280
-                //if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3281
-                //indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3282
-                //extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3283
-                //of array('Registration.TXN_ID'=>23)
3284
-                $query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3285
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3286
-                    if (! is_array($possibly_array_of_params)) {
3287
-                        throw new EE_Error(sprintf(__("You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3288
-                            "event_espresso"),
3289
-                            $param, $possibly_array_of_params));
3290
-                    }
3291
-                    $this->_extract_related_models_from_sub_params_array_keys(
3292
-                        $possibly_array_of_params,
3293
-                        $model_query_info_carrier, $query_param_type
3294
-                    );
3295
-                } elseif ($query_param_type === 0 //ie WHERE
3296
-                          && is_array($possibly_array_of_params)
3297
-                          && isset($possibly_array_of_params[2])
3298
-                          && $possibly_array_of_params[2] == true
3299
-                ) {
3300
-                    //then $possible_array_of_params looks something like array('<','DTT_sold',true)
3301
-                    //indicating that $possible_array_of_params[1] is actually a field name,
3302
-                    //from which we should extract query parameters!
3303
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3304
-                        throw new EE_Error(sprintf(__("Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3305
-                            "event_espresso"), $query_param_type, implode(",", $possibly_array_of_params)));
3306
-                    }
3307
-                    $this->_extract_related_model_info_from_query_param($possibly_array_of_params[1],
3308
-                        $model_query_info_carrier, $query_param_type);
3309
-                }
3310
-            }
3311
-        }
3312
-        return $model_query_info_carrier;
3313
-    }
3314
-
3315
-
3316
-
3317
-    /**
3318
-     * For extracting related models from forced_joins, where the array values contain the info about what
3319
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3320
-     *
3321
-     * @param array                       $sub_query_params like EEM_Base::get_all's $query_params[0] or
3322
-     *                                                      $query_params['having']
3323
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3324
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3325
-     * @throws EE_Error
3326
-     * @return \EE_Model_Query_Info_Carrier
3327
-     */
3328
-    private function _extract_related_models_from_sub_params_array_values(
3329
-        $sub_query_params,
3330
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3331
-        $query_param_type
3332
-    ) {
3333
-        if (! empty($sub_query_params)) {
3334
-            if (! is_array($sub_query_params)) {
3335
-                throw new EE_Error(sprintf(__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3336
-                    $sub_query_params));
3337
-            }
3338
-            foreach ($sub_query_params as $param) {
3339
-                //$param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3340
-                $this->_extract_related_model_info_from_query_param($param, $model_query_info_carrier,
3341
-                    $query_param_type);
3342
-            }
3343
-        }
3344
-        return $model_query_info_carrier;
3345
-    }
3346
-
3347
-
3348
-
3349
-    /**
3350
-     * Extract all the query parts from $query_params (an array like whats passed to EEM_Base::get_all)
3351
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3352
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3353
-     * but use them in a different order. Eg, we need to know what models we are querying
3354
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3355
-     * other models before we can finalize the where clause SQL.
3356
-     *
3357
-     * @param array $query_params
3358
-     * @throws EE_Error
3359
-     * @return EE_Model_Query_Info_Carrier
3360
-     */
3361
-    public function _create_model_query_info_carrier($query_params)
3362
-    {
3363
-        if (! is_array($query_params)) {
3364
-            EE_Error::doing_it_wrong(
3365
-                'EEM_Base::_create_model_query_info_carrier',
3366
-                sprintf(
3367
-                    __(
3368
-                        '$query_params should be an array, you passed a variable of type %s',
3369
-                        'event_espresso'
3370
-                    ),
3371
-                    gettype($query_params)
3372
-                ),
3373
-                '4.6.0'
3374
-            );
3375
-            $query_params = array();
3376
-        }
3377
-        $where_query_params = isset($query_params[0]) ? $query_params[0] : array();
3378
-        //first check if we should alter the query to account for caps or not
3379
-        //because the caps might require us to do extra joins
3380
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3381
-            $query_params[0] = $where_query_params = array_replace_recursive(
3382
-                $where_query_params,
3383
-                $this->caps_where_conditions(
3384
-                    $query_params['caps']
3385
-                )
3386
-            );
3387
-        }
3388
-        $query_object = $this->_extract_related_models_from_query($query_params);
3389
-        //verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3390
-        foreach ($where_query_params as $key => $value) {
3391
-            if (is_int($key)) {
3392
-                throw new EE_Error(
3393
-                    sprintf(
3394
-                        __(
3395
-                            "WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3396
-                            "event_espresso"
3397
-                        ),
3398
-                        $key,
3399
-                        var_export($value, true),
3400
-                        var_export($query_params, true),
3401
-                        get_class($this)
3402
-                    )
3403
-                );
3404
-            }
3405
-        }
3406
-        if (
3407
-            array_key_exists('default_where_conditions', $query_params)
3408
-            && ! empty($query_params['default_where_conditions'])
3409
-        ) {
3410
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3411
-        } else {
3412
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3413
-        }
3414
-        $where_query_params = array_merge(
3415
-            $this->_get_default_where_conditions_for_models_in_query(
3416
-                $query_object,
3417
-                $use_default_where_conditions,
3418
-                $where_query_params
3419
-            ),
3420
-            $where_query_params
3421
-        );
3422
-        $query_object->set_where_sql($this->_construct_where_clause($where_query_params));
3423
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3424
-        // So we need to setup a subquery and use that for the main join.
3425
-        // Note for now this only works on the primary table for the model.
3426
-        // So for instance, you could set the limit array like this:
3427
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3428
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3429
-            $query_object->set_main_model_join_sql(
3430
-                $this->_construct_limit_join_select(
3431
-                    $query_params['on_join_limit'][0],
3432
-                    $query_params['on_join_limit'][1]
3433
-                )
3434
-            );
3435
-        }
3436
-        //set limit
3437
-        if (array_key_exists('limit', $query_params)) {
3438
-            if (is_array($query_params['limit'])) {
3439
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3440
-                    $e = sprintf(
3441
-                        __(
3442
-                            "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3443
-                            "event_espresso"
3444
-                        ),
3445
-                        http_build_query($query_params['limit'])
3446
-                    );
3447
-                    throw new EE_Error($e . "|" . $e);
3448
-                }
3449
-                //they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3450
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3451
-            } elseif (! empty ($query_params['limit'])) {
3452
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3453
-            }
3454
-        }
3455
-        //set order by
3456
-        if (array_key_exists('order_by', $query_params)) {
3457
-            if (is_array($query_params['order_by'])) {
3458
-                //if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3459
-                //specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3460
-                //including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3461
-                if (array_key_exists('order', $query_params)) {
3462
-                    throw new EE_Error(
3463
-                        sprintf(
3464
-                            __(
3465
-                                "In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3466
-                                "event_espresso"
3467
-                            ),
3468
-                            get_class($this),
3469
-                            implode(", ", array_keys($query_params['order_by'])),
3470
-                            implode(", ", $query_params['order_by']),
3471
-                            $query_params['order']
3472
-                        )
3473
-                    );
3474
-                }
3475
-                $this->_extract_related_models_from_sub_params_array_keys(
3476
-                    $query_params['order_by'],
3477
-                    $query_object,
3478
-                    'order_by'
3479
-                );
3480
-                //assume it's an array of fields to order by
3481
-                $order_array = array();
3482
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3483
-                    $order = $this->_extract_order($order);
3484
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3485
-                }
3486
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3487
-            } elseif (! empty ($query_params['order_by'])) {
3488
-                $this->_extract_related_model_info_from_query_param(
3489
-                    $query_params['order_by'],
3490
-                    $query_object,
3491
-                    'order',
3492
-                    $query_params['order_by']
3493
-                );
3494
-                $order = isset($query_params['order'])
3495
-                    ? $this->_extract_order($query_params['order'])
3496
-                    : 'DESC';
3497
-                $query_object->set_order_by_sql(
3498
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3499
-                );
3500
-            }
3501
-        }
3502
-        //if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3503
-        if (! array_key_exists('order_by', $query_params)
3504
-            && array_key_exists('order', $query_params)
3505
-            && ! empty($query_params['order'])
3506
-        ) {
3507
-            $pk_field = $this->get_primary_key_field();
3508
-            $order = $this->_extract_order($query_params['order']);
3509
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3510
-        }
3511
-        //set group by
3512
-        if (array_key_exists('group_by', $query_params)) {
3513
-            if (is_array($query_params['group_by'])) {
3514
-                //it's an array, so assume we'll be grouping by a bunch of stuff
3515
-                $group_by_array = array();
3516
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3517
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3518
-                }
3519
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3520
-            } elseif (! empty ($query_params['group_by'])) {
3521
-                $query_object->set_group_by_sql(
3522
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3523
-                );
3524
-            }
3525
-        }
3526
-        //set having
3527
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3528
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3529
-        }
3530
-        //now, just verify they didn't pass anything wack
3531
-        foreach ($query_params as $query_key => $query_value) {
3532
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3533
-                throw new EE_Error(
3534
-                    sprintf(
3535
-                        __(
3536
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3537
-                            'event_espresso'
3538
-                        ),
3539
-                        $query_key,
3540
-                        get_class($this),
3541
-                        //						print_r( $this->_allowed_query_params, TRUE )
3542
-                        implode(',', $this->_allowed_query_params)
3543
-                    )
3544
-                );
3545
-            }
3546
-        }
3547
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3548
-        if (empty($main_model_join_sql)) {
3549
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3550
-        }
3551
-        return $query_object;
3552
-    }
3553
-
3554
-
3555
-
3556
-    /**
3557
-     * Gets the where conditions that should be imposed on the query based on the
3558
-     * context (eg reading frontend, backend, edit or delete).
3559
-     *
3560
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3561
-     * @return array like EEM_Base::get_all() 's $query_params[0]
3562
-     * @throws EE_Error
3563
-     */
3564
-    public function caps_where_conditions($context = self::caps_read)
3565
-    {
3566
-        EEM_Base::verify_is_valid_cap_context($context);
3567
-        $cap_where_conditions = array();
3568
-        $cap_restrictions = $this->caps_missing($context);
3569
-        /**
3570
-         * @var $cap_restrictions EE_Default_Where_Conditions[]
3571
-         */
3572
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3573
-            $cap_where_conditions = array_replace_recursive($cap_where_conditions,
3574
-                $restriction_if_no_cap->get_default_where_conditions());
3575
-        }
3576
-        return apply_filters('FHEE__EEM_Base__caps_where_conditions__return', $cap_where_conditions, $this, $context,
3577
-            $cap_restrictions);
3578
-    }
3579
-
3580
-
3581
-
3582
-    /**
3583
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3584
-     * otherwise throws an exception
3585
-     *
3586
-     * @param string $should_be_order_string
3587
-     * @return string either ASC, asc, DESC or desc
3588
-     * @throws EE_Error
3589
-     */
3590
-    private function _extract_order($should_be_order_string)
3591
-    {
3592
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3593
-            return $should_be_order_string;
3594
-        }
3595
-        throw new EE_Error(
3596
-            sprintf(
3597
-                __(
3598
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3599
-                    "event_espresso"
3600
-                ), get_class($this), $should_be_order_string
3601
-            )
3602
-        );
3603
-    }
3604
-
3605
-
3606
-
3607
-    /**
3608
-     * Looks at all the models which are included in this query, and asks each
3609
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3610
-     * so they can be merged
3611
-     *
3612
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3613
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3614
-     *                                                                  'none' means NO default where conditions will
3615
-     *                                                                  be used AT ALL during this query.
3616
-     *                                                                  'other_models_only' means default where
3617
-     *                                                                  conditions from other models will be used, but
3618
-     *                                                                  not for this primary model. 'all', the default,
3619
-     *                                                                  means default where conditions will apply as
3620
-     *                                                                  normal
3621
-     * @param array                       $where_query_params           like EEM_Base::get_all's $query_params[0]
3622
-     * @throws EE_Error
3623
-     * @return array like $query_params[0], see EEM_Base::get_all for documentation
3624
-     */
3625
-    private function _get_default_where_conditions_for_models_in_query(
3626
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3627
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3628
-        $where_query_params = array()
3629
-    ) {
3630
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3631
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3632
-            throw new EE_Error(sprintf(__("You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3633
-                "event_espresso"), $use_default_where_conditions,
3634
-                implode(", ", $allowed_used_default_where_conditions_values)));
3635
-        }
3636
-        $universal_query_params = array();
3637
-        if ($this->_should_use_default_where_conditions( $use_default_where_conditions, true)) {
3638
-            $universal_query_params = $this->_get_default_where_conditions();
3639
-        } else if ($this->_should_use_minimum_where_conditions( $use_default_where_conditions, true)) {
3640
-            $universal_query_params = $this->_get_minimum_where_conditions();
3641
-        }
3642
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3643
-            $related_model = $this->get_related_model_obj($model_name);
3644
-            if ( $this->_should_use_default_where_conditions( $use_default_where_conditions, false)) {
3645
-                $related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3646
-            } elseif ($this->_should_use_minimum_where_conditions( $use_default_where_conditions, false)) {
3647
-                $related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3648
-            } else {
3649
-                //we don't want to add full or even minimum default where conditions from this model, so just continue
3650
-                continue;
3651
-            }
3652
-            $overrides = $this->_override_defaults_or_make_null_friendly(
3653
-                $related_model_universal_where_params,
3654
-                $where_query_params,
3655
-                $related_model,
3656
-                $model_relation_path
3657
-            );
3658
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3659
-                $universal_query_params,
3660
-                $overrides
3661
-            );
3662
-        }
3663
-        return $universal_query_params;
3664
-    }
3665
-
3666
-
3667
-
3668
-    /**
3669
-     * Determines whether or not we should use default where conditions for the model in question
3670
-     * (this model, or other related models).
3671
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3672
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3673
-     * We should use default where conditions on related models when they requested to use default where conditions
3674
-     * on all models, or specifically just on other related models
3675
-     * @param      $default_where_conditions_value
3676
-     * @param bool $for_this_model false means this is for OTHER related models
3677
-     * @return bool
3678
-     */
3679
-    private function _should_use_default_where_conditions( $default_where_conditions_value, $for_this_model = true )
3680
-    {
3681
-        return (
3682
-                   $for_this_model
3683
-                   && in_array(
3684
-                       $default_where_conditions_value,
3685
-                       array(
3686
-                           EEM_Base::default_where_conditions_all,
3687
-                           EEM_Base::default_where_conditions_this_only,
3688
-                           EEM_Base::default_where_conditions_minimum_others,
3689
-                       ),
3690
-                       true
3691
-                   )
3692
-               )
3693
-               || (
3694
-                   ! $for_this_model
3695
-                   && in_array(
3696
-                       $default_where_conditions_value,
3697
-                       array(
3698
-                           EEM_Base::default_where_conditions_all,
3699
-                           EEM_Base::default_where_conditions_others_only,
3700
-                       ),
3701
-                       true
3702
-                   )
3703
-               );
3704
-    }
3705
-
3706
-    /**
3707
-     * Determines whether or not we should use default minimum conditions for the model in question
3708
-     * (this model, or other related models).
3709
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3710
-     * where conditions.
3711
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3712
-     * on this model or others
3713
-     * @param      $default_where_conditions_value
3714
-     * @param bool $for_this_model false means this is for OTHER related models
3715
-     * @return bool
3716
-     */
3717
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3718
-    {
3719
-        return (
3720
-                   $for_this_model
3721
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3722
-               )
3723
-               || (
3724
-                   ! $for_this_model
3725
-                   && in_array(
3726
-                       $default_where_conditions_value,
3727
-                       array(
3728
-                           EEM_Base::default_where_conditions_minimum_others,
3729
-                           EEM_Base::default_where_conditions_minimum_all,
3730
-                       ),
3731
-                       true
3732
-                   )
3733
-               );
3734
-    }
3735
-
3736
-
3737
-    /**
3738
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3739
-     * then we also add a special where condition which allows for that model's primary key
3740
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3741
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3742
-     *
3743
-     * @param array    $default_where_conditions
3744
-     * @param array    $provided_where_conditions
3745
-     * @param EEM_Base $model
3746
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3747
-     * @return array like EEM_Base::get_all's $query_params[0]
3748
-     * @throws EE_Error
3749
-     */
3750
-    private function _override_defaults_or_make_null_friendly(
3751
-        $default_where_conditions,
3752
-        $provided_where_conditions,
3753
-        $model,
3754
-        $model_relation_path
3755
-    ) {
3756
-        $null_friendly_where_conditions = array();
3757
-        $none_overridden = true;
3758
-        $or_condition_key_for_defaults = 'OR*' . get_class($model);
3759
-        foreach ($default_where_conditions as $key => $val) {
3760
-            if (isset($provided_where_conditions[$key])) {
3761
-                $none_overridden = false;
3762
-            } else {
3763
-                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3764
-            }
3765
-        }
3766
-        if ($none_overridden && $default_where_conditions) {
3767
-            if ($model->has_primary_key_field()) {
3768
-                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3769
-                                                                                . "."
3770
-                                                                                . $model->primary_key_name()] = array('IS NULL');
3771
-            }/*else{
37
+	/**
38
+	 * Flag to indicate whether the values provided to EEM_Base have already been prepared
39
+	 * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
40
+	 * They almost always WILL NOT, but it's not necessarily a requirement.
41
+	 * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
42
+	 *
43
+	 * @var boolean
44
+	 */
45
+	private $_values_already_prepared_by_model_object = 0;
46
+
47
+	/**
48
+	 * when $_values_already_prepared_by_model_object equals this, we assume
49
+	 * the data is just like form input that needs to have the model fields'
50
+	 * prepare_for_set and prepare_for_use_in_db called on it
51
+	 */
52
+	const not_prepared_by_model_object = 0;
53
+
54
+	/**
55
+	 * when $_values_already_prepared_by_model_object equals this, we
56
+	 * assume this value is coming from a model object and doesn't need to have
57
+	 * prepare_for_set called on it, just prepare_for_use_in_db is used
58
+	 */
59
+	const prepared_by_model_object = 1;
60
+
61
+	/**
62
+	 * when $_values_already_prepared_by_model_object equals this, we assume
63
+	 * the values are already to be used in the database (ie no processing is done
64
+	 * on them by the model's fields)
65
+	 */
66
+	const prepared_for_use_in_db = 2;
67
+
68
+
69
+	protected $singular_item = 'Item';
70
+
71
+	protected $plural_item   = 'Items';
72
+
73
+	/**
74
+	 * @type \EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
75
+	 */
76
+	protected $_tables;
77
+
78
+	/**
79
+	 * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
80
+	 * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
81
+	 * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
82
+	 *
83
+	 * @var \EE_Model_Field_Base[][] $_fields
84
+	 */
85
+	protected $_fields;
86
+
87
+	/**
88
+	 * array of different kinds of relations
89
+	 *
90
+	 * @var \EE_Model_Relation_Base[] $_model_relations
91
+	 */
92
+	protected $_model_relations;
93
+
94
+	/**
95
+	 * @var \EE_Index[] $_indexes
96
+	 */
97
+	protected $_indexes = array();
98
+
99
+	/**
100
+	 * Default strategy for getting where conditions on this model. This strategy is used to get default
101
+	 * where conditions which are added to get_all, update, and delete queries. They can be overridden
102
+	 * by setting the same columns as used in these queries in the query yourself.
103
+	 *
104
+	 * @var EE_Default_Where_Conditions
105
+	 */
106
+	protected $_default_where_conditions_strategy;
107
+
108
+	/**
109
+	 * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
110
+	 * This is particularly useful when you want something between 'none' and 'default'
111
+	 *
112
+	 * @var EE_Default_Where_Conditions
113
+	 */
114
+	protected $_minimum_where_conditions_strategy;
115
+
116
+	/**
117
+	 * String describing how to find the "owner" of this model's objects.
118
+	 * When there is a foreign key on this model to the wp_users table, this isn't needed.
119
+	 * But when there isn't, this indicates which related model, or transiently-related model,
120
+	 * has the foreign key to the wp_users table.
121
+	 * Eg, for EEM_Registration this would be 'Event' because registrations are directly
122
+	 * related to events, and events have a foreign key to wp_users.
123
+	 * On EEM_Transaction, this would be 'Transaction.Event'
124
+	 *
125
+	 * @var string
126
+	 */
127
+	protected $_model_chain_to_wp_user = '';
128
+
129
+	/**
130
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
131
+	 * don't need it (particularly CPT models)
132
+	 *
133
+	 * @var bool
134
+	 */
135
+	protected $_ignore_where_strategy = false;
136
+
137
+	/**
138
+	 * String used in caps relating to this model. Eg, if the caps relating to this
139
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
140
+	 *
141
+	 * @var string. If null it hasn't been initialized yet. If false then we
142
+	 * have indicated capabilities don't apply to this
143
+	 */
144
+	protected $_caps_slug = null;
145
+
146
+	/**
147
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
148
+	 * and next-level keys are capability names, and each's value is a
149
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
150
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
151
+	 * and then each capability in the corresponding sub-array that they're missing
152
+	 * adds the where conditions onto the query.
153
+	 *
154
+	 * @var array
155
+	 */
156
+	protected $_cap_restrictions = array(
157
+		self::caps_read       => array(),
158
+		self::caps_read_admin => array(),
159
+		self::caps_edit       => array(),
160
+		self::caps_delete     => array(),
161
+	);
162
+
163
+	/**
164
+	 * Array defining which cap restriction generators to use to create default
165
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
166
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
167
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
168
+	 * automatically set this to false (not just null).
169
+	 *
170
+	 * @var EE_Restriction_Generator_Base[]
171
+	 */
172
+	protected $_cap_restriction_generators = array();
173
+
174
+	/**
175
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
176
+	 */
177
+	const caps_read       = 'read';
178
+
179
+	const caps_read_admin = 'read_admin';
180
+
181
+	const caps_edit       = 'edit';
182
+
183
+	const caps_delete     = 'delete';
184
+
185
+	/**
186
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
187
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
188
+	 * maps to 'read' because when looking for relevant permissions we're going to use
189
+	 * 'read' in teh capabilities names like 'ee_read_events' etc.
190
+	 *
191
+	 * @var array
192
+	 */
193
+	protected $_cap_contexts_to_cap_action_map = array(
194
+		self::caps_read       => 'read',
195
+		self::caps_read_admin => 'read',
196
+		self::caps_edit       => 'edit',
197
+		self::caps_delete     => 'delete',
198
+	);
199
+
200
+	/**
201
+	 * Timezone
202
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
203
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
204
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
205
+	 * EE_Datetime_Field data type will have access to it.
206
+	 *
207
+	 * @var string
208
+	 */
209
+	protected $_timezone;
210
+
211
+
212
+	/**
213
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
214
+	 * multisite.
215
+	 *
216
+	 * @var int
217
+	 */
218
+	protected static $_model_query_blog_id;
219
+
220
+	/**
221
+	 * A copy of _fields, except the array keys are the model names pointed to by
222
+	 * the field
223
+	 *
224
+	 * @var EE_Model_Field_Base[]
225
+	 */
226
+	private $_cache_foreign_key_to_fields = array();
227
+
228
+	/**
229
+	 * Cached list of all the fields on the model, indexed by their name
230
+	 *
231
+	 * @var EE_Model_Field_Base[]
232
+	 */
233
+	private $_cached_fields = null;
234
+
235
+	/**
236
+	 * Cached list of all the fields on the model, except those that are
237
+	 * marked as only pertinent to the database
238
+	 *
239
+	 * @var EE_Model_Field_Base[]
240
+	 */
241
+	private $_cached_fields_non_db_only = null;
242
+
243
+	/**
244
+	 * A cached reference to the primary key for quick lookup
245
+	 *
246
+	 * @var EE_Model_Field_Base
247
+	 */
248
+	private $_primary_key_field = null;
249
+
250
+	/**
251
+	 * Flag indicating whether this model has a primary key or not
252
+	 *
253
+	 * @var boolean
254
+	 */
255
+	protected $_has_primary_key_field = null;
256
+
257
+	/**
258
+	 * Whether or not this model is based off a table in WP core only (CPTs should set
259
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
260
+	 * This should be true for models that deal with data that should exist independent of EE.
261
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
262
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
263
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
264
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
265
+	 * @var boolean
266
+	 */
267
+	protected $_wp_core_model = false;
268
+
269
+	/**
270
+	 *    List of valid operators that can be used for querying.
271
+	 * The keys are all operators we'll accept, the values are the real SQL
272
+	 * operators used
273
+	 *
274
+	 * @var array
275
+	 */
276
+	protected $_valid_operators = array(
277
+		'='           => '=',
278
+		'<='          => '<=',
279
+		'<'           => '<',
280
+		'>='          => '>=',
281
+		'>'           => '>',
282
+		'!='          => '!=',
283
+		'LIKE'        => 'LIKE',
284
+		'like'        => 'LIKE',
285
+		'NOT_LIKE'    => 'NOT LIKE',
286
+		'not_like'    => 'NOT LIKE',
287
+		'NOT LIKE'    => 'NOT LIKE',
288
+		'not like'    => 'NOT LIKE',
289
+		'IN'          => 'IN',
290
+		'in'          => 'IN',
291
+		'NOT_IN'      => 'NOT IN',
292
+		'not_in'      => 'NOT IN',
293
+		'NOT IN'      => 'NOT IN',
294
+		'not in'      => 'NOT IN',
295
+		'between'     => 'BETWEEN',
296
+		'BETWEEN'     => 'BETWEEN',
297
+		'IS_NOT_NULL' => 'IS NOT NULL',
298
+		'is_not_null' => 'IS NOT NULL',
299
+		'IS NOT NULL' => 'IS NOT NULL',
300
+		'is not null' => 'IS NOT NULL',
301
+		'IS_NULL'     => 'IS NULL',
302
+		'is_null'     => 'IS NULL',
303
+		'IS NULL'     => 'IS NULL',
304
+		'is null'     => 'IS NULL',
305
+		'REGEXP'      => 'REGEXP',
306
+		'regexp'      => 'REGEXP',
307
+		'NOT_REGEXP'  => 'NOT REGEXP',
308
+		'not_regexp'  => 'NOT REGEXP',
309
+		'NOT REGEXP'  => 'NOT REGEXP',
310
+		'not regexp'  => 'NOT REGEXP',
311
+	);
312
+
313
+	/**
314
+	 * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
315
+	 *
316
+	 * @var array
317
+	 */
318
+	protected $_in_style_operators = array('IN', 'NOT IN');
319
+
320
+	/**
321
+	 * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
322
+	 * '12-31-2012'"
323
+	 *
324
+	 * @var array
325
+	 */
326
+	protected $_between_style_operators = array('BETWEEN');
327
+
328
+	/**
329
+	 * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
330
+	 * @var array
331
+	 */
332
+	protected $_like_style_operators = array('LIKE', 'NOT LIKE');
333
+	/**
334
+	 * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
335
+	 * on a join table.
336
+	 *
337
+	 * @var array
338
+	 */
339
+	protected $_null_style_operators = array('IS NOT NULL', 'IS NULL');
340
+
341
+	/**
342
+	 * Allowed values for $query_params['order'] for ordering in queries
343
+	 *
344
+	 * @var array
345
+	 */
346
+	protected $_allowed_order_values = array('asc', 'desc', 'ASC', 'DESC');
347
+
348
+	/**
349
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
350
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
351
+	 *
352
+	 * @var array
353
+	 */
354
+	private $_logic_query_param_keys = array('not', 'and', 'or', 'NOT', 'AND', 'OR');
355
+
356
+	/**
357
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
358
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
359
+	 *
360
+	 * @var array
361
+	 */
362
+	private $_allowed_query_params = array(
363
+		0,
364
+		'limit',
365
+		'order_by',
366
+		'group_by',
367
+		'having',
368
+		'force_join',
369
+		'order',
370
+		'on_join_limit',
371
+		'default_where_conditions',
372
+		'caps',
373
+		'extra_selects'
374
+	);
375
+
376
+	/**
377
+	 * All the data types that can be used in $wpdb->prepare statements.
378
+	 *
379
+	 * @var array
380
+	 */
381
+	private $_valid_wpdb_data_types = array('%d', '%s', '%f');
382
+
383
+	/**
384
+	 * @var EE_Registry $EE
385
+	 */
386
+	protected $EE = null;
387
+
388
+
389
+	/**
390
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
391
+	 *
392
+	 * @var int
393
+	 */
394
+	protected $_show_next_x_db_queries = 0;
395
+
396
+	/**
397
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
398
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
399
+	 * WHERE, GROUP_BY, etc.
400
+	 *
401
+	 * @var CustomSelects
402
+	 */
403
+	protected $_custom_selections = array();
404
+
405
+	/**
406
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
407
+	 * caches every model object we've fetched from the DB on this request
408
+	 *
409
+	 * @var array
410
+	 */
411
+	protected $_entity_map;
412
+
413
+	/**
414
+	 * @var LoaderInterface $loader
415
+	 */
416
+	private static $loader;
417
+
418
+
419
+	/**
420
+	 * constant used to show EEM_Base has not yet verified the db on this http request
421
+	 */
422
+	const db_verified_none = 0;
423
+
424
+	/**
425
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
426
+	 * but not the addons' dbs
427
+	 */
428
+	const db_verified_core = 1;
429
+
430
+	/**
431
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
432
+	 * the EE core db too)
433
+	 */
434
+	const db_verified_addons = 2;
435
+
436
+	/**
437
+	 * indicates whether an EEM_Base child has already re-verified the DB
438
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
439
+	 * looking like EEM_Base::db_verified_*
440
+	 *
441
+	 * @var int - 0 = none, 1 = core, 2 = addons
442
+	 */
443
+	protected static $_db_verification_level = EEM_Base::db_verified_none;
444
+
445
+	/**
446
+	 * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
447
+	 *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
448
+	 *        registrations for non-trashed tickets for non-trashed datetimes)
449
+	 */
450
+	const default_where_conditions_all = 'all';
451
+
452
+	/**
453
+	 * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
454
+	 *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
455
+	 *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
456
+	 *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
457
+	 *        models which share tables with other models, this can return data for the wrong model.
458
+	 */
459
+	const default_where_conditions_this_only = 'this_model_only';
460
+
461
+	/**
462
+	 * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
463
+	 *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
464
+	 *        return all registrations related to non-trashed tickets and non-trashed datetimes)
465
+	 */
466
+	const default_where_conditions_others_only = 'other_models_only';
467
+
468
+	/**
469
+	 * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
470
+	 *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
471
+	 *        their table with other models, like the Event and Venue models. For example, when querying for events
472
+	 *        ordered by their venues' name, this will be sure to only return real events with associated real venues
473
+	 *        (regardless of whether those events and venues are trashed)
474
+	 *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
475
+	 *        events.
476
+	 */
477
+	const default_where_conditions_minimum_all = 'minimum';
478
+
479
+	/**
480
+	 * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
481
+	 *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
482
+	 *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
483
+	 *        not)
484
+	 */
485
+	const default_where_conditions_minimum_others = 'full_this_minimum_others';
486
+
487
+	/**
488
+	 * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
489
+	 *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
490
+	 *        it's possible it will return table entries for other models. You should use
491
+	 *        EEM_Base::default_where_conditions_minimum_all instead.
492
+	 */
493
+	const default_where_conditions_none = 'none';
494
+
495
+
496
+
497
+	/**
498
+	 * About all child constructors:
499
+	 * they should define the _tables, _fields and _model_relations arrays.
500
+	 * Should ALWAYS be called after child constructor.
501
+	 * In order to make the child constructors to be as simple as possible, this parent constructor
502
+	 * finalizes constructing all the object's attributes.
503
+	 * Generally, rather than requiring a child to code
504
+	 * $this->_tables = array(
505
+	 *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
506
+	 *        ...);
507
+	 *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
508
+	 * each EE_Table has a function to set the table's alias after the constructor, using
509
+	 * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
510
+	 * do something similar.
511
+	 *
512
+	 * @param null $timezone
513
+	 * @throws EE_Error
514
+	 */
515
+	protected function __construct($timezone = null)
516
+	{
517
+		// check that the model has not been loaded too soon
518
+		if (! did_action('AHEE__EE_System__load_espresso_addons')) {
519
+			throw new EE_Error (
520
+				sprintf(
521
+					__('The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
522
+						'event_espresso'),
523
+					get_class($this)
524
+				)
525
+			);
526
+		}
527
+		/**
528
+		 * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
529
+		 */
530
+		if (empty(EEM_Base::$_model_query_blog_id)) {
531
+			EEM_Base::set_model_query_blog_id();
532
+		}
533
+		/**
534
+		 * Filters the list of tables on a model. It is best to NOT use this directly and instead
535
+		 * just use EE_Register_Model_Extension
536
+		 *
537
+		 * @var EE_Table_Base[] $_tables
538
+		 */
539
+		$this->_tables = (array)apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
540
+		foreach ($this->_tables as $table_alias => $table_obj) {
541
+			/** @var $table_obj EE_Table_Base */
542
+			$table_obj->_construct_finalize_with_alias($table_alias);
543
+			if ($table_obj instanceof EE_Secondary_Table) {
544
+				/** @var $table_obj EE_Secondary_Table */
545
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
546
+			}
547
+		}
548
+		/**
549
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
550
+		 * EE_Register_Model_Extension
551
+		 *
552
+		 * @param EE_Model_Field_Base[] $_fields
553
+		 */
554
+		$this->_fields = (array)apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
555
+		$this->_invalidate_field_caches();
556
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
557
+			if (! array_key_exists($table_alias, $this->_tables)) {
558
+				throw new EE_Error(sprintf(__("Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
559
+					'event_espresso'), $table_alias, implode(",", $this->_fields)));
560
+			}
561
+			foreach ($fields_for_table as $field_name => $field_obj) {
562
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
563
+				//primary key field base has a slightly different _construct_finalize
564
+				/** @var $field_obj EE_Model_Field_Base */
565
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
566
+			}
567
+		}
568
+		// everything is related to Extra_Meta
569
+		if (get_class($this) !== 'EEM_Extra_Meta') {
570
+			//make extra meta related to everything, but don't block deleting things just
571
+			//because they have related extra meta info. For now just orphan those extra meta
572
+			//in the future we should automatically delete them
573
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
574
+		}
575
+		//and change logs
576
+		if (get_class($this) !== 'EEM_Change_Log') {
577
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
578
+		}
579
+		/**
580
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
581
+		 * EE_Register_Model_Extension
582
+		 *
583
+		 * @param EE_Model_Relation_Base[] $_model_relations
584
+		 */
585
+		$this->_model_relations = (array)apply_filters('FHEE__' . get_class($this) . '__construct__model_relations',
586
+			$this->_model_relations);
587
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
588
+			/** @var $relation_obj EE_Model_Relation_Base */
589
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
590
+		}
591
+		foreach ($this->_indexes as $index_name => $index_obj) {
592
+			/** @var $index_obj EE_Index */
593
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
594
+		}
595
+		$this->set_timezone($timezone);
596
+		//finalize default where condition strategy, or set default
597
+		if (! $this->_default_where_conditions_strategy) {
598
+			//nothing was set during child constructor, so set default
599
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
600
+		}
601
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
602
+		if (! $this->_minimum_where_conditions_strategy) {
603
+			//nothing was set during child constructor, so set default
604
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
605
+		}
606
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
607
+		//if the cap slug hasn't been set, and we haven't set it to false on purpose
608
+		//to indicate to NOT set it, set it to the logical default
609
+		if ($this->_caps_slug === null) {
610
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
611
+		}
612
+		//initialize the standard cap restriction generators if none were specified by the child constructor
613
+		if ($this->_cap_restriction_generators !== false) {
614
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
615
+				if (! isset($this->_cap_restriction_generators[$cap_context])) {
616
+					$this->_cap_restriction_generators[$cap_context] = apply_filters(
617
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
618
+						new EE_Restriction_Generator_Protected(),
619
+						$cap_context,
620
+						$this
621
+					);
622
+				}
623
+			}
624
+		}
625
+		//if there are cap restriction generators, use them to make the default cap restrictions
626
+		if ($this->_cap_restriction_generators !== false) {
627
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
628
+				if (! $generator_object) {
629
+					continue;
630
+				}
631
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
632
+					throw new EE_Error(
633
+						sprintf(
634
+							__('Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
635
+								'event_espresso'),
636
+							$context,
637
+							$this->get_this_model_name()
638
+						)
639
+					);
640
+				}
641
+				$action = $this->cap_action_for_context($context);
642
+				if (! $generator_object->construction_finalized()) {
643
+					$generator_object->_construct_finalize($this, $action);
644
+				}
645
+			}
646
+		}
647
+		do_action('AHEE__' . get_class($this) . '__construct__end');
648
+	}
649
+
650
+
651
+
652
+	/**
653
+	 * Used to set the $_model_query_blog_id static property.
654
+	 *
655
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
656
+	 *                      value for get_current_blog_id() will be used.
657
+	 */
658
+	public static function set_model_query_blog_id($blog_id = 0)
659
+	{
660
+		EEM_Base::$_model_query_blog_id = $blog_id > 0 ? (int)$blog_id : get_current_blog_id();
661
+	}
662
+
663
+
664
+
665
+	/**
666
+	 * Returns whatever is set as the internal $model_query_blog_id.
667
+	 *
668
+	 * @return int
669
+	 */
670
+	public static function get_model_query_blog_id()
671
+	{
672
+		return EEM_Base::$_model_query_blog_id;
673
+	}
674
+
675
+
676
+
677
+	/**
678
+	 * This function is a singleton method used to instantiate the Espresso_model object
679
+	 *
680
+	 * @param string $timezone string representing the timezone we want to set for returned Date Time Strings
681
+	 *                                (and any incoming timezone data that gets saved).
682
+	 *                                Note this just sends the timezone info to the date time model field objects.
683
+	 *                                Default is NULL
684
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
685
+	 * @return static (as in the concrete child class)
686
+	 * @throws EE_Error
687
+	 * @throws InvalidArgumentException
688
+	 * @throws InvalidDataTypeException
689
+	 * @throws InvalidInterfaceException
690
+	 */
691
+	public static function instance($timezone = null)
692
+	{
693
+		// check if instance of Espresso_model already exists
694
+		if (! static::$_instance instanceof static) {
695
+			// instantiate Espresso_model
696
+			static::$_instance = new static(
697
+				$timezone,
698
+				LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
699
+			);
700
+		}
701
+		//we might have a timezone set, let set_timezone decide what to do with it
702
+		static::$_instance->set_timezone($timezone);
703
+		// Espresso_model object
704
+		return static::$_instance;
705
+	}
706
+
707
+
708
+
709
+	/**
710
+	 * resets the model and returns it
711
+	 *
712
+	 * @param null | string $timezone
713
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
714
+	 * all its properties reset; if it wasn't instantiated, returns null)
715
+	 * @throws EE_Error
716
+	 * @throws ReflectionException
717
+	 * @throws InvalidArgumentException
718
+	 * @throws InvalidDataTypeException
719
+	 * @throws InvalidInterfaceException
720
+	 */
721
+	public static function reset($timezone = null)
722
+	{
723
+		if (static::$_instance instanceof EEM_Base) {
724
+			//let's try to NOT swap out the current instance for a new one
725
+			//because if someone has a reference to it, we can't remove their reference
726
+			//so it's best to keep using the same reference, but change the original object
727
+			//reset all its properties to their original values as defined in the class
728
+			$r = new ReflectionClass(get_class(static::$_instance));
729
+			$static_properties = $r->getStaticProperties();
730
+			foreach ($r->getDefaultProperties() as $property => $value) {
731
+				//don't set instance to null like it was originally,
732
+				//but it's static anyways, and we're ignoring static properties (for now at least)
733
+				if (! isset($static_properties[$property])) {
734
+					static::$_instance->{$property} = $value;
735
+				}
736
+			}
737
+			//and then directly call its constructor again, like we would if we were creating a new one
738
+			static::$_instance->__construct(
739
+				$timezone,
740
+				LoaderFactory::getLoader()->load('EventEspresso\core\services\orm\ModelFieldFactory')
741
+			);
742
+			return self::instance();
743
+		}
744
+		return null;
745
+	}
746
+
747
+
748
+
749
+	/**
750
+	 * @return LoaderInterface
751
+	 * @throws InvalidArgumentException
752
+	 * @throws InvalidDataTypeException
753
+	 * @throws InvalidInterfaceException
754
+	 */
755
+	private static function getLoader()
756
+	{
757
+		if(! EEM_Base::$loader instanceof LoaderInterface) {
758
+			EEM_Base::$loader = LoaderFactory::getLoader();
759
+		}
760
+		return EEM_Base::$loader;
761
+	}
762
+
763
+
764
+
765
+	/**
766
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
767
+	 *
768
+	 * @param  boolean $translated return localized strings or JUST the array.
769
+	 * @return array
770
+	 * @throws EE_Error
771
+	 * @throws InvalidArgumentException
772
+	 * @throws InvalidDataTypeException
773
+	 * @throws InvalidInterfaceException
774
+	 */
775
+	public function status_array($translated = false)
776
+	{
777
+		if (! array_key_exists('Status', $this->_model_relations)) {
778
+			return array();
779
+		}
780
+		$model_name = $this->get_this_model_name();
781
+		$status_type = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
782
+		$stati = EEM_Status::instance()->get_all(array(array('STS_type' => $status_type)));
783
+		$status_array = array();
784
+		foreach ($stati as $status) {
785
+			$status_array[$status->ID()] = $status->get('STS_code');
786
+		}
787
+		return $translated
788
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
789
+			: $status_array;
790
+	}
791
+
792
+
793
+
794
+	/**
795
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
796
+	 *
797
+	 * @param array $query_params             {
798
+	 * @var array $0 (where) array {
799
+	 *                                        eg: array('QST_display_text'=>'Are you bob?','QST_admin_text'=>'Determine
800
+	 *                                        if user is bob') becomes SQL >> "...WHERE QST_display_text = 'Are you
801
+	 *                                        bob?' AND QST_admin_text = 'Determine if user is bob'...") To add WHERE
802
+	 *                                        conditions based on related models (and even
803
+	 *                                        models-related-to-related-models) prepend the model's name onto the field
804
+	 *                                        name. Eg,
805
+	 *                                        EEM_Event::instance()->get_all(array(array('Venue.VNU_ID'=>12))); becomes
806
+	 *                                        SQL >> "SELECT * FROM wp_posts AS Event_CPT LEFT JOIN wp_esp_event_meta
807
+	 *                                        AS Event_Meta ON Event_CPT.ID = Event_Meta.EVT_ID LEFT JOIN
808
+	 *                                        wp_esp_event_venue AS Event_Venue ON Event_Venue.EVT_ID=Event_CPT.ID LEFT
809
+	 *                                        JOIN wp_posts AS Venue_CPT ON Venue_CPT.ID=Event_Venue.VNU_ID LEFT JOIN
810
+	 *                                        wp_esp_venue_meta AS Venue_Meta ON Venue_CPT.ID = Venue_Meta.VNU_ID WHERE
811
+	 *                                        Venue_CPT.ID = 12 Notice that automatically took care of joining Events
812
+	 *                                        to Venues (even when each of those models actually consisted of two
813
+	 *                                        tables). Also, you may chain the model relations together. Eg instead of
814
+	 *                                        just having
815
+	 *                                        "Venue.VNU_ID", you could have
816
+	 *                                        "Registration.Attendee.ATT_ID" as a field on a query for events (because
817
+	 *                                        events are related to Registrations, which are related to Attendees). You
818
+	 *                                        can take it even further with
819
+	 *                                        "Registration.Transaction.Payment.PAY_amount" etc. To change the operator
820
+	 *                                        (from the default of '='), change the value to an numerically-indexed
821
+	 *                                        array, where the first item in the list is the operator. eg: array(
822
+	 *                                        'QST_display_text' => array('LIKE','%bob%'), 'QST_ID' => array('<',34),
823
+	 *                                        'QST_wp_user' => array('in',array(1,2,7,23))) becomes SQL >> "...WHERE
824
+	 *                                        QST_display_text LIKE '%bob%' AND QST_ID < 34 AND QST_wp_user IN
825
+	 *                                        (1,2,7,23)...". Valid operators so far: =, !=, <, <=, >, >=, LIKE, NOT
826
+	 *                                        LIKE, IN (followed by numeric-indexed array), NOT IN (dido), BETWEEN
827
+	 *                                        (followed by an array with exactly 2 date strings), IS NULL, and IS NOT
828
+	 *                                        NULL Values can be a string, int, or float. They can also be arrays IFF
829
+	 *                                        the operator is IN. Also, values can actually be field names. To indicate
830
+	 *                                        the value is a field, simply provide a third array item (true) to the
831
+	 *                                        operator-value array like so: eg: array( 'DTT_reg_limit' => array('>',
832
+	 *                                        'DTT_sold', TRUE) ) becomes SQL >> "...WHERE DTT_reg_limit > DTT_sold"
833
+	 *                                        Note: you can also use related model field names like you would any other
834
+	 *                                        field name. eg:
835
+	 *                                        array('Datetime.DTT_reg_limit'=>array('=','Datetime.DTT_sold',TRUE) could
836
+	 *                                        be used if you were querying EEM_Tickets (because Datetime is directly related to tickets) Also, by default all the where conditions are AND'd together. To override this, add an array key 'OR' (or 'AND') and the array to be OR'd together eg: array('OR'=>array('TXN_ID' => 23 , 'TXN_timestamp__>' =>
837
+	 *                                        345678912)) becomes SQL >> "...WHERE TXN_ID = 23 OR TXN_timestamp =
838
+	 *                                        345678912...". Also, to negate an entire set of conditions, use 'NOT' as
839
+	 *                                        an array key. eg: array('NOT'=>array('TXN_total' =>
840
+	 *                                        50, 'TXN_paid'=>23) becomes SQL >> "...where ! (TXN_total =50 AND
841
+	 *                                        TXN_paid =23) Note: the 'glue' used to join each condition will continue
842
+	 *                                        to be what you last specified. IE, "AND"s by default, but if you had
843
+	 *                                        previously specified to use ORs to join, ORs will continue to be used.
844
+	 *                                        So, if you specify to use an "OR" to join conditions, it will continue to
845
+	 *                                        "stick" until you specify an AND. eg
846
+	 *                                        array('OR'=>array('NOT'=>array('TXN_total' => 50,
847
+	 *                                        'TXN_paid'=>23)),AND=>array('TXN_ID'=>1,'STS_ID'=>'TIN') becomes SQL >>
848
+	 *                                        "...where ! (TXN_total =50 OR TXN_paid =23) AND TXN_ID=1 AND
849
+	 *                                        STS_ID='TIN'" They can be nested indefinitely. eg:
850
+	 *                                        array('OR'=>array('TXN_total' => 23, 'NOT'=> array( 'TXN_timestamp'=> 345678912, 'AND'=>array('TXN_paid' => 53, 'STS_ID' => 'TIN')))) becomes SQL >> "...WHERE TXN_total = 23 OR ! (TXN_timestamp = 345678912 OR (TXN_paid = 53 AND STS_ID = 'TIN'))..." GOTCHA: because this is an array, array keys must be unique, making it impossible to place two or more where conditions applying to the same field. eg: array('PAY_timestamp'=>array('>',$start_date),'PAY_timestamp'=>array('<',$end_date),'PAY_timestamp'=>array('!=',$special_date)), as PHP enforces that the array keys must be unique, thus removing the first two array entries with key 'PAY_timestamp'. becomes SQL >> "PAY_timestamp !=  4234232", ignoring the first two PAY_timestamp conditions). To overcome this, you can add a '*' character to the end of the field's name, followed by anything. These will be removed when generating the SQL string, but allow for the array keys to be unique. eg: you could rewrite the previous query as: array('PAY_timestamp'=>array('>',$start_date),'PAY_timestamp*1st'=>array('<',$end_date),'PAY_timestamp*2nd'=>array('!=',$special_date)) which correctly becomes SQL >>
851
+	 *                                        "PAY_timestamp > 123412341 AND PAY_timestamp < 2354235235234 AND
852
+	 *                                        PAY_timestamp != 1241234123" This can be applied to condition operators
853
+	 *                                        too, eg:
854
+	 *                                        array('OR'=>array('REG_ID'=>3,'Transaction.TXN_ID'=>23),'OR*whatever'=>array('Attendee.ATT_fname'=>'bob','Attendee.ATT_lname'=>'wilson')));
855
+	 * @var mixed   $limit                    int|array    adds a limit to the query just like the SQL limit clause, so
856
+	 *                                        limits of "23", "25,50", and array(23,42) are all valid would become SQL
857
+	 *                                        "...LIMIT 23", "...LIMIT 25,50", and "...LIMIT 23,42" respectively.
858
+	 *                                        Remember when you provide two numbers for the limit, the 1st number is
859
+	 *                                        the OFFSET, the 2nd is the LIMIT
860
+	 * @var array   $on_join_limit            allows the setting of a special select join with a internal limit so you
861
+	 *                                        can do paging on one-to-many multi-table-joins. Send an array in the
862
+	 *                                        following format array('on_join_limit'
863
+	 *                                        => array( 'table_alias', array(1,2) ) ).
864
+	 * @var mixed   $order_by                 name of a column to order by, or an array where keys are field names and
865
+	 *                                        values are either 'ASC' or 'DESC'.
866
+	 *                                        'limit'=>array('STS_ID'=>'ASC','REG_date'=>'DESC'), which would becomes
867
+	 *                                        SQL "...ORDER BY TXN_timestamp..." and "...ORDER BY STS_ID ASC, REG_date
868
+	 *                                        DESC..." respectively. Like the
869
+	 *                                        'where' conditions, these fields can be on related models. Eg
870
+	 *                                        'order_by'=>array('Registration.Transaction.TXN_amount'=>'ASC') is
871
+	 *                                        perfectly valid from any model related to 'Registration' (like Event,
872
+	 *                                        Attendee, Price, Datetime, etc.)
873
+	 * @var string  $order                    If 'order_by' is used and its value is a string (NOT an array), then
874
+	 *                                        'order' specifies whether to order the field specified in 'order_by' in
875
+	 *                                        ascending or descending order. Acceptable values are 'ASC' or 'DESC'. If,
876
+	 *                                        'order_by' isn't used, but 'order' is, then it is assumed you want to
877
+	 *                                        order by the primary key. Eg,
878
+	 *                                        EEM_Event::instance()->get_all(array('order_by'=>'Datetime.DTT_EVT_start','order'=>'ASC');
879
+	 *                                        //(will join with the Datetime model's table(s) and order by its field
880
+	 *                                        DTT_EVT_start) or
881
+	 *                                        EEM_Registration::instance()->get_all(array('order'=>'ASC'));//will make
882
+	 *                                        SQL "SELECT * FROM wp_esp_registration ORDER BY REG_ID ASC"
883
+	 * @var mixed   $group_by                 name of field to order by, or an array of fields. Eg either
884
+	 *                                        'group_by'=>'VNU_ID', or
885
+	 *                                        'group_by'=>array('EVT_name','Registration.Transaction.TXN_total') Note:
886
+	 *                                        if no
887
+	 *                                        $group_by is specified, and a limit is set, automatically groups by the
888
+	 *                                        model's primary key (or combined primary keys). This avoids some
889
+	 *                                        weirdness that results when using limits, tons of joins, and no group by,
890
+	 *                                        see https://events.codebasehq.com/projects/event-espresso/tickets/9389
891
+	 * @var array   $having                   exactly like WHERE parameters array, except these conditions apply to the
892
+	 *                                        grouped results (whereas WHERE conditions apply to the pre-grouped
893
+	 *                                        results)
894
+	 * @var array   $force_join               forces a join with the models named. Should be a numerically-indexed
895
+	 *                                        array where values are models to be joined in the query.Eg
896
+	 *                                        array('Attendee','Payment','Datetime'). You may join with transient
897
+	 *                                        models using period, eg "Registration.Transaction.Payment". You will
898
+	 *                                        probably only want to do this in hopes of increasing efficiency, as
899
+	 *                                        related models which belongs to the current model
900
+	 *                                        (ie, the current model has a foreign key to them, like how Registration
901
+	 *                                        belongs to Attendee) can be cached in order to avoid future queries
902
+	 * @var string  $default_where_conditions can be set to 'none', 'this_model_only', 'other_models_only', or 'all'.
903
+	 *                                        set this to 'none' to disable all default where conditions. Eg, usually
904
+	 *                                        soft-deleted objects are filtered-out if you want to include them, set
905
+	 *                                        this query param to 'none'. If you want to ONLY disable THIS model's
906
+	 *                                        default where conditions set it to 'other_models_only'. If you only want
907
+	 *                                        this model's default where conditions added to the query, use
908
+	 *                                        'this_model_only'. If you want to use all default where conditions
909
+	 *                                        (default), set to 'all'.
910
+	 * @var string  $caps                     controls what capability requirements to apply to the query; ie, should
911
+	 *                                        we just NOT apply any capabilities/permissions/restrictions and return
912
+	 *                                        everything? Or should we only show the current user items they should be
913
+	 *                                        able to view on the frontend, backend, edit, or delete? can be set to
914
+	 *                                        'none' (default), 'read_frontend', 'read_backend', 'edit' or 'delete'
915
+	 *                                        }
916
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
917
+	 *                                        from EE_Base_Class[], use _get_all_wpdb_results()and make it public
918
+	 *                                        again. Array keys are object IDs (if there is a primary key on the model.
919
+	 *                                        if not, numerically indexed) Some full examples: get 10 transactions
920
+	 *                                        which have Scottish attendees: EEM_Transaction::instance()->get_all(
921
+	 *                                        array( array(
922
+	 *                                        'OR'=>array(
923
+	 *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
924
+	 *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
925
+	 *                                        )
926
+	 *                                        ),
927
+	 *                                        'limit'=>10,
928
+	 *                                        'group_by'=>'TXN_ID'
929
+	 *                                        ));
930
+	 *                                        get all the answers to the question titled "shirt size" for event with id
931
+	 *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
932
+	 *                                        'Question.QST_display_text'=>'shirt size',
933
+	 *                                        'Registration.Event.EVT_ID'=>12
934
+	 *                                        ),
935
+	 *                                        'order_by'=>array('ANS_value'=>'ASC')
936
+	 *                                        ));
937
+	 * @throws EE_Error
938
+	 */
939
+	public function get_all($query_params = array())
940
+	{
941
+		if (isset($query_params['limit'])
942
+			&& ! isset($query_params['group_by'])
943
+		) {
944
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
945
+		}
946
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params, ARRAY_A, null));
947
+	}
948
+
949
+
950
+
951
+	/**
952
+	 * Modifies the query parameters so we only get back model objects
953
+	 * that "belong" to the current user
954
+	 *
955
+	 * @param array $query_params @see EEM_Base::get_all()
956
+	 * @return array like EEM_Base::get_all
957
+	 */
958
+	public function alter_query_params_to_only_include_mine($query_params = array())
959
+	{
960
+		$wp_user_field_name = $this->wp_user_field_name();
961
+		if ($wp_user_field_name) {
962
+			$query_params[0][$wp_user_field_name] = get_current_user_id();
963
+		}
964
+		return $query_params;
965
+	}
966
+
967
+
968
+
969
+	/**
970
+	 * Returns the name of the field's name that points to the WP_User table
971
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
972
+	 * foreign key to the WP_User table)
973
+	 *
974
+	 * @return string|boolean string on success, boolean false when there is no
975
+	 * foreign key to the WP_User table
976
+	 */
977
+	public function wp_user_field_name()
978
+	{
979
+		try {
980
+			if (! empty($this->_model_chain_to_wp_user)) {
981
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
982
+				$last_model_name = end($models_to_follow_to_wp_users);
983
+				$model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
984
+				$model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
985
+			} else {
986
+				$model_with_fk_to_wp_users = $this;
987
+				$model_chain_to_wp_user = '';
988
+			}
989
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
990
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
991
+		} catch (EE_Error $e) {
992
+			return false;
993
+		}
994
+	}
995
+
996
+
997
+
998
+	/**
999
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
1000
+	 * (or transiently-related model) has a foreign key to the wp_users table;
1001
+	 * useful for finding if model objects of this type are 'owned' by the current user.
1002
+	 * This is an empty string when the foreign key is on this model and when it isn't,
1003
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
1004
+	 * (or transiently-related model)
1005
+	 *
1006
+	 * @return string
1007
+	 */
1008
+	public function model_chain_to_wp_user()
1009
+	{
1010
+		return $this->_model_chain_to_wp_user;
1011
+	}
1012
+
1013
+
1014
+
1015
+	/**
1016
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1017
+	 * like how registrations don't have a foreign key to wp_users, but the
1018
+	 * events they are for are), or is unrelated to wp users.
1019
+	 * generally available
1020
+	 *
1021
+	 * @return boolean
1022
+	 */
1023
+	public function is_owned()
1024
+	{
1025
+		if ($this->model_chain_to_wp_user()) {
1026
+			return true;
1027
+		}
1028
+		try {
1029
+			$this->get_foreign_key_to('WP_User');
1030
+			return true;
1031
+		} catch (EE_Error $e) {
1032
+			return false;
1033
+		}
1034
+	}
1035
+
1036
+
1037
+	/**
1038
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1039
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1040
+	 * the model)
1041
+	 *
1042
+	 * @param array  $query_params      like EEM_Base::get_all's $query_params
1043
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1044
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1045
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1046
+	 *                                  override this and set the select to "*", or a specific column name, like
1047
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1048
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1049
+	 *                                  the aliases used to refer to this selection, and values are to be
1050
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1051
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1052
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1053
+	 * @throws EE_Error
1054
+	 * @throws InvalidArgumentException
1055
+	 */
1056
+	protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1057
+	{
1058
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);;
1059
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1060
+		$select_expressions = $columns_to_select === null
1061
+			? $this->_construct_default_select_sql($model_query_info)
1062
+			: '';
1063
+		if ($this->_custom_selections instanceof CustomSelects) {
1064
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1065
+			$select_expressions .= $select_expressions
1066
+				? ', ' . $custom_expressions
1067
+				: $custom_expressions;
1068
+		}
1069
+
1070
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1071
+		return $this->_do_wpdb_query('get_results', array($SQL, $output));
1072
+	}
1073
+
1074
+
1075
+	/**
1076
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1077
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1078
+	 * method of including extra select information.
1079
+	 *
1080
+	 * @param array             $query_params
1081
+	 * @param null|array|string $columns_to_select
1082
+	 * @return null|CustomSelects
1083
+	 * @throws InvalidArgumentException
1084
+	 */
1085
+	protected function getCustomSelection(array $query_params, $columns_to_select = null)
1086
+	{
1087
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1088
+			return null;
1089
+		}
1090
+		$selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1091
+		$selects = is_string($selects) ? explode(',', $selects) : $selects;
1092
+		return new CustomSelects($selects);
1093
+	}
1094
+
1095
+
1096
+
1097
+	/**
1098
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1099
+	 * but you can use the $query_params like on EEM_Base::get_all() to more easily
1100
+	 * take care of joins, field preparation etc.
1101
+	 *
1102
+	 * @param array  $query_params      like EEM_Base::get_all's $query_params
1103
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1104
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1105
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1106
+	 *                                  override this and set the select to "*", or a specific column name, like
1107
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1108
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1109
+	 *                                  the aliases used to refer to this selection, and values are to be
1110
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1111
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1112
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1113
+	 * @throws EE_Error
1114
+	 */
1115
+	public function get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1116
+	{
1117
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1118
+	}
1119
+
1120
+
1121
+
1122
+	/**
1123
+	 * For creating a custom select statement
1124
+	 *
1125
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1126
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1127
+	 *                                 SQL, and 1=>is the datatype
1128
+	 * @throws EE_Error
1129
+	 * @return string
1130
+	 */
1131
+	private function _construct_select_from_input($columns_to_select)
1132
+	{
1133
+		if (is_array($columns_to_select)) {
1134
+			$select_sql_array = array();
1135
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1136
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1137
+					throw new EE_Error(
1138
+						sprintf(
1139
+							__(
1140
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1141
+								'event_espresso'
1142
+							),
1143
+							$selection_and_datatype,
1144
+							$alias
1145
+						)
1146
+					);
1147
+				}
1148
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1149
+					throw new EE_Error(
1150
+						sprintf(
1151
+							esc_html__(
1152
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1153
+								'event_espresso'
1154
+							),
1155
+							$selection_and_datatype[1],
1156
+							$selection_and_datatype[0],
1157
+							$alias,
1158
+							implode(', ', $this->_valid_wpdb_data_types)
1159
+						)
1160
+					);
1161
+				}
1162
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1163
+			}
1164
+			$columns_to_select_string = implode(', ', $select_sql_array);
1165
+		} else {
1166
+			$columns_to_select_string = $columns_to_select;
1167
+		}
1168
+		return $columns_to_select_string;
1169
+	}
1170
+
1171
+
1172
+
1173
+	/**
1174
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1175
+	 *
1176
+	 * @return string
1177
+	 * @throws EE_Error
1178
+	 */
1179
+	public function primary_key_name()
1180
+	{
1181
+		return $this->get_primary_key_field()->get_name();
1182
+	}
1183
+
1184
+
1185
+
1186
+	/**
1187
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1188
+	 * If there is no primary key on this model, $id is treated as primary key string
1189
+	 *
1190
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1191
+	 * @return EE_Base_Class
1192
+	 */
1193
+	public function get_one_by_ID($id)
1194
+	{
1195
+		if ($this->get_from_entity_map($id)) {
1196
+			return $this->get_from_entity_map($id);
1197
+		}
1198
+		return $this->get_one(
1199
+			$this->alter_query_params_to_restrict_by_ID(
1200
+				$id,
1201
+				array('default_where_conditions' => EEM_Base::default_where_conditions_minimum_all)
1202
+			)
1203
+		);
1204
+	}
1205
+
1206
+
1207
+
1208
+	/**
1209
+	 * Alters query parameters to only get items with this ID are returned.
1210
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1211
+	 * or could just be a simple primary key ID
1212
+	 *
1213
+	 * @param int   $id
1214
+	 * @param array $query_params
1215
+	 * @return array of normal query params, @see EEM_Base::get_all
1216
+	 * @throws EE_Error
1217
+	 */
1218
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1219
+	{
1220
+		if (! isset($query_params[0])) {
1221
+			$query_params[0] = array();
1222
+		}
1223
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1224
+		if ($conditions_from_id === null) {
1225
+			$query_params[0][$this->primary_key_name()] = $id;
1226
+		} else {
1227
+			//no primary key, so the $id must be from the get_index_primary_key_string()
1228
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1229
+		}
1230
+		return $query_params;
1231
+	}
1232
+
1233
+
1234
+
1235
+	/**
1236
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1237
+	 * array. If no item is found, null is returned.
1238
+	 *
1239
+	 * @param array $query_params like EEM_Base's $query_params variable.
1240
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1241
+	 * @throws EE_Error
1242
+	 */
1243
+	public function get_one($query_params = array())
1244
+	{
1245
+		if (! is_array($query_params)) {
1246
+			EE_Error::doing_it_wrong('EEM_Base::get_one',
1247
+				sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1248
+					gettype($query_params)), '4.6.0');
1249
+			$query_params = array();
1250
+		}
1251
+		$query_params['limit'] = 1;
1252
+		$items = $this->get_all($query_params);
1253
+		if (empty($items)) {
1254
+			return null;
1255
+		}
1256
+		return array_shift($items);
1257
+	}
1258
+
1259
+
1260
+
1261
+	/**
1262
+	 * Returns the next x number of items in sequence from the given value as
1263
+	 * found in the database matching the given query conditions.
1264
+	 *
1265
+	 * @param mixed $current_field_value    Value used for the reference point.
1266
+	 * @param null  $field_to_order_by      What field is used for the
1267
+	 *                                      reference point.
1268
+	 * @param int   $limit                  How many to return.
1269
+	 * @param array $query_params           Extra conditions on the query.
1270
+	 * @param null  $columns_to_select      If left null, then an array of
1271
+	 *                                      EE_Base_Class objects is returned,
1272
+	 *                                      otherwise you can indicate just the
1273
+	 *                                      columns you want returned.
1274
+	 * @return EE_Base_Class[]|array
1275
+	 * @throws EE_Error
1276
+	 */
1277
+	public function next_x(
1278
+		$current_field_value,
1279
+		$field_to_order_by = null,
1280
+		$limit = 1,
1281
+		$query_params = array(),
1282
+		$columns_to_select = null
1283
+	) {
1284
+		return $this->_get_consecutive(
1285
+			$current_field_value,
1286
+			'>',
1287
+			$field_to_order_by,
1288
+			$limit,
1289
+			$query_params,
1290
+			$columns_to_select
1291
+		);
1292
+	}
1293
+
1294
+
1295
+
1296
+	/**
1297
+	 * Returns the previous x number of items in sequence from the given value
1298
+	 * as found in the database matching the given query conditions.
1299
+	 *
1300
+	 * @param mixed $current_field_value    Value used for the reference point.
1301
+	 * @param null  $field_to_order_by      What field is used for the
1302
+	 *                                      reference point.
1303
+	 * @param int   $limit                  How many to return.
1304
+	 * @param array $query_params           Extra conditions on the query.
1305
+	 * @param null  $columns_to_select      If left null, then an array of
1306
+	 *                                      EE_Base_Class objects is returned,
1307
+	 *                                      otherwise you can indicate just the
1308
+	 *                                      columns you want returned.
1309
+	 * @return EE_Base_Class[]|array
1310
+	 * @throws EE_Error
1311
+	 */
1312
+	public function previous_x(
1313
+		$current_field_value,
1314
+		$field_to_order_by = null,
1315
+		$limit = 1,
1316
+		$query_params = array(),
1317
+		$columns_to_select = null
1318
+	) {
1319
+		return $this->_get_consecutive(
1320
+			$current_field_value,
1321
+			'<',
1322
+			$field_to_order_by,
1323
+			$limit,
1324
+			$query_params,
1325
+			$columns_to_select
1326
+		);
1327
+	}
1328
+
1329
+
1330
+
1331
+	/**
1332
+	 * Returns the next item in sequence from the given value as found in the
1333
+	 * database matching the given query conditions.
1334
+	 *
1335
+	 * @param mixed $current_field_value    Value used for the reference point.
1336
+	 * @param null  $field_to_order_by      What field is used for the
1337
+	 *                                      reference point.
1338
+	 * @param array $query_params           Extra conditions on the query.
1339
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1340
+	 *                                      object is returned, otherwise you
1341
+	 *                                      can indicate just the columns you
1342
+	 *                                      want and a single array indexed by
1343
+	 *                                      the columns will be returned.
1344
+	 * @return EE_Base_Class|null|array()
1345
+	 * @throws EE_Error
1346
+	 */
1347
+	public function next(
1348
+		$current_field_value,
1349
+		$field_to_order_by = null,
1350
+		$query_params = array(),
1351
+		$columns_to_select = null
1352
+	) {
1353
+		$results = $this->_get_consecutive(
1354
+			$current_field_value,
1355
+			'>',
1356
+			$field_to_order_by,
1357
+			1,
1358
+			$query_params,
1359
+			$columns_to_select
1360
+		);
1361
+		return empty($results) ? null : reset($results);
1362
+	}
1363
+
1364
+
1365
+
1366
+	/**
1367
+	 * Returns the previous item in sequence from the given value as found in
1368
+	 * the database matching the given query conditions.
1369
+	 *
1370
+	 * @param mixed $current_field_value    Value used for the reference point.
1371
+	 * @param null  $field_to_order_by      What field is used for the
1372
+	 *                                      reference point.
1373
+	 * @param array $query_params           Extra conditions on the query.
1374
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1375
+	 *                                      object is returned, otherwise you
1376
+	 *                                      can indicate just the columns you
1377
+	 *                                      want and a single array indexed by
1378
+	 *                                      the columns will be returned.
1379
+	 * @return EE_Base_Class|null|array()
1380
+	 * @throws EE_Error
1381
+	 */
1382
+	public function previous(
1383
+		$current_field_value,
1384
+		$field_to_order_by = null,
1385
+		$query_params = array(),
1386
+		$columns_to_select = null
1387
+	) {
1388
+		$results = $this->_get_consecutive(
1389
+			$current_field_value,
1390
+			'<',
1391
+			$field_to_order_by,
1392
+			1,
1393
+			$query_params,
1394
+			$columns_to_select
1395
+		);
1396
+		return empty($results) ? null : reset($results);
1397
+	}
1398
+
1399
+
1400
+
1401
+	/**
1402
+	 * Returns the a consecutive number of items in sequence from the given
1403
+	 * value as found in the database matching the given query conditions.
1404
+	 *
1405
+	 * @param mixed  $current_field_value   Value used for the reference point.
1406
+	 * @param string $operand               What operand is used for the sequence.
1407
+	 * @param string $field_to_order_by     What field is used for the reference point.
1408
+	 * @param int    $limit                 How many to return.
1409
+	 * @param array  $query_params          Extra conditions on the query.
1410
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1411
+	 *                                      otherwise you can indicate just the columns you want returned.
1412
+	 * @return EE_Base_Class[]|array
1413
+	 * @throws EE_Error
1414
+	 */
1415
+	protected function _get_consecutive(
1416
+		$current_field_value,
1417
+		$operand = '>',
1418
+		$field_to_order_by = null,
1419
+		$limit = 1,
1420
+		$query_params = array(),
1421
+		$columns_to_select = null
1422
+	) {
1423
+		//if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1424
+		if (empty($field_to_order_by)) {
1425
+			if ($this->has_primary_key_field()) {
1426
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1427
+			} else {
1428
+				if (WP_DEBUG) {
1429
+					throw new EE_Error(__('EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1430
+						'event_espresso'));
1431
+				}
1432
+				EE_Error::add_error(__('There was an error with the query.', 'event_espresso'));
1433
+				return array();
1434
+			}
1435
+		}
1436
+		if (! is_array($query_params)) {
1437
+			EE_Error::doing_it_wrong('EEM_Base::_get_consecutive',
1438
+				sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1439
+					gettype($query_params)), '4.6.0');
1440
+			$query_params = array();
1441
+		}
1442
+		//let's add the where query param for consecutive look up.
1443
+		$query_params[0][$field_to_order_by] = array($operand, $current_field_value);
1444
+		$query_params['limit'] = $limit;
1445
+		//set direction
1446
+		$incoming_orderby = isset($query_params['order_by']) ? (array)$query_params['order_by'] : array();
1447
+		$query_params['order_by'] = $operand === '>'
1448
+			? array($field_to_order_by => 'ASC') + $incoming_orderby
1449
+			: array($field_to_order_by => 'DESC') + $incoming_orderby;
1450
+		//if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1451
+		if (empty($columns_to_select)) {
1452
+			return $this->get_all($query_params);
1453
+		}
1454
+		//getting just the fields
1455
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1456
+	}
1457
+
1458
+
1459
+
1460
+	/**
1461
+	 * This sets the _timezone property after model object has been instantiated.
1462
+	 *
1463
+	 * @param null | string $timezone valid PHP DateTimeZone timezone string
1464
+	 */
1465
+	public function set_timezone($timezone)
1466
+	{
1467
+		if ($timezone !== null) {
1468
+			$this->_timezone = $timezone;
1469
+		}
1470
+		//note we need to loop through relations and set the timezone on those objects as well.
1471
+		foreach ($this->_model_relations as $relation) {
1472
+			$relation->set_timezone($timezone);
1473
+		}
1474
+		//and finally we do the same for any datetime fields
1475
+		foreach ($this->_fields as $field) {
1476
+			if ($field instanceof EE_Datetime_Field) {
1477
+				$field->set_timezone($timezone);
1478
+			}
1479
+		}
1480
+	}
1481
+
1482
+
1483
+
1484
+	/**
1485
+	 * This just returns whatever is set for the current timezone.
1486
+	 *
1487
+	 * @access public
1488
+	 * @return string
1489
+	 */
1490
+	public function get_timezone()
1491
+	{
1492
+		//first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1493
+		if (empty($this->_timezone)) {
1494
+			foreach ($this->_fields as $field) {
1495
+				if ($field instanceof EE_Datetime_Field) {
1496
+					$this->set_timezone($field->get_timezone());
1497
+					break;
1498
+				}
1499
+			}
1500
+		}
1501
+		//if timezone STILL empty then return the default timezone for the site.
1502
+		if (empty($this->_timezone)) {
1503
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1504
+		}
1505
+		return $this->_timezone;
1506
+	}
1507
+
1508
+
1509
+
1510
+	/**
1511
+	 * This returns the date formats set for the given field name and also ensures that
1512
+	 * $this->_timezone property is set correctly.
1513
+	 *
1514
+	 * @since 4.6.x
1515
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1516
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1517
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1518
+	 * @return array formats in an array with the date format first, and the time format last.
1519
+	 */
1520
+	public function get_formats_for($field_name, $pretty = false)
1521
+	{
1522
+		$field_settings = $this->field_settings_for($field_name);
1523
+		//if not a valid EE_Datetime_Field then throw error
1524
+		if (! $field_settings instanceof EE_Datetime_Field) {
1525
+			throw new EE_Error(sprintf(__('The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1526
+				'event_espresso'), $field_name));
1527
+		}
1528
+		//while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1529
+		//the field.
1530
+		$this->_timezone = $field_settings->get_timezone();
1531
+		return array($field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty));
1532
+	}
1533
+
1534
+
1535
+
1536
+	/**
1537
+	 * This returns the current time in a format setup for a query on this model.
1538
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1539
+	 * it will return:
1540
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1541
+	 *  NOW
1542
+	 *  - or a unix timestamp (equivalent to time())
1543
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1544
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1545
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1546
+	 * @since 4.6.x
1547
+	 * @param string $field_name       The field the current time is needed for.
1548
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1549
+	 *                                 formatted string matching the set format for the field in the set timezone will
1550
+	 *                                 be returned.
1551
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1552
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1553
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1554
+	 *                                 exception is triggered.
1555
+	 */
1556
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1557
+	{
1558
+		$formats = $this->get_formats_for($field_name);
1559
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1560
+		if ($timestamp) {
1561
+			return $DateTime->format('U');
1562
+		}
1563
+		//not returning timestamp, so return formatted string in timezone.
1564
+		switch ($what) {
1565
+			case 'time' :
1566
+				return $DateTime->format($formats[1]);
1567
+				break;
1568
+			case 'date' :
1569
+				return $DateTime->format($formats[0]);
1570
+				break;
1571
+			default :
1572
+				return $DateTime->format(implode(' ', $formats));
1573
+				break;
1574
+		}
1575
+	}
1576
+
1577
+
1578
+
1579
+	/**
1580
+	 * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1581
+	 * for the model are.  Returns a DateTime object.
1582
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1583
+	 * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1584
+	 * ignored.
1585
+	 *
1586
+	 * @param string $field_name      The field being setup.
1587
+	 * @param string $timestring      The date time string being used.
1588
+	 * @param string $incoming_format The format for the time string.
1589
+	 * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1590
+	 *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1591
+	 *                                format is
1592
+	 *                                'U', this is ignored.
1593
+	 * @return DateTime
1594
+	 * @throws EE_Error
1595
+	 */
1596
+	public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1597
+	{
1598
+		//just using this to ensure the timezone is set correctly internally
1599
+		$this->get_formats_for($field_name);
1600
+		//load EEH_DTT_Helper
1601
+		$set_timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1602
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1603
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1604
+		return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1605
+	}
1606
+
1607
+
1608
+
1609
+	/**
1610
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1611
+	 *
1612
+	 * @return EE_Table_Base[]
1613
+	 */
1614
+	public function get_tables()
1615
+	{
1616
+		return $this->_tables;
1617
+	}
1618
+
1619
+
1620
+
1621
+	/**
1622
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1623
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1624
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1625
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1626
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1627
+	 * model object with EVT_ID = 1
1628
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1629
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1630
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1631
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1632
+	 * are not specified)
1633
+	 *
1634
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1635
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1636
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1637
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1638
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1639
+	 *                                         ID=34, we'd use this method as follows:
1640
+	 *                                         EEM_Transaction::instance()->update(
1641
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1642
+	 *                                         array(array('TXN_ID'=>34)));
1643
+	 * @param array   $query_params            very much like EEM_Base::get_all's $query_params
1644
+	 *                                         in client code into what's expected to be stored on each field. Eg,
1645
+	 *                                         consider updating Question's QST_admin_label field is of type
1646
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1647
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1648
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1649
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1650
+	 *                                         TRUE, it is assumed that you've already called
1651
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1652
+	 *                                         malicious javascript. However, if
1653
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1654
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1655
+	 *                                         and every other field, before insertion. We provide this parameter
1656
+	 *                                         because model objects perform their prepare_for_set function on all
1657
+	 *                                         their values, and so don't need to be called again (and in many cases,
1658
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1659
+	 *                                         prepare_for_set method...)
1660
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1661
+	 *                                         in this model's entity map according to $fields_n_values that match
1662
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1663
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1664
+	 *                                         could get out-of-sync with the database
1665
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1666
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1667
+	 *                                         bad)
1668
+	 * @throws EE_Error
1669
+	 */
1670
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1671
+	{
1672
+		if (! is_array($query_params)) {
1673
+			EE_Error::doing_it_wrong('EEM_Base::update',
1674
+				sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1675
+					gettype($query_params)), '4.6.0');
1676
+			$query_params = array();
1677
+		}
1678
+		/**
1679
+		 * Action called before a model update call has been made.
1680
+		 *
1681
+		 * @param EEM_Base $model
1682
+		 * @param array    $fields_n_values the updated fields and their new values
1683
+		 * @param array    $query_params    @see EEM_Base::get_all()
1684
+		 */
1685
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1686
+		/**
1687
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1688
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1689
+		 *
1690
+		 * @param array    $fields_n_values fields and their new values
1691
+		 * @param EEM_Base $model           the model being queried
1692
+		 * @param array    $query_params    see EEM_Base::get_all()
1693
+		 */
1694
+		$fields_n_values = (array)apply_filters('FHEE__EEM_Base__update__fields_n_values', $fields_n_values, $this,
1695
+			$query_params);
1696
+		//need to verify that, for any entry we want to update, there are entries in each secondary table.
1697
+		//to do that, for each table, verify that it's PK isn't null.
1698
+		$tables = $this->get_tables();
1699
+		//and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1700
+		//NOTE: we should make this code more efficient by NOT querying twice
1701
+		//before the real update, but that needs to first go through ALPHA testing
1702
+		//as it's dangerous. says Mike August 8 2014
1703
+		//we want to make sure the default_where strategy is ignored
1704
+		$this->_ignore_where_strategy = true;
1705
+		$wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1706
+		foreach ($wpdb_select_results as $wpdb_result) {
1707
+			// type cast stdClass as array
1708
+			$wpdb_result = (array)$wpdb_result;
1709
+			//get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1710
+			if ($this->has_primary_key_field()) {
1711
+				$main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1712
+			} else {
1713
+				//if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1714
+				$main_table_pk_value = null;
1715
+			}
1716
+			//if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1717
+			//and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1718
+			if (count($tables) > 1) {
1719
+				//foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1720
+				//in that table, and so we'll want to insert one
1721
+				foreach ($tables as $table_obj) {
1722
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1723
+					//if there is no private key for this table on the results, it means there's no entry
1724
+					//in this table, right? so insert a row in the current table, using any fields available
1725
+					if (! (array_key_exists($this_table_pk_column, $wpdb_result)
1726
+						   && $wpdb_result[$this_table_pk_column])
1727
+					) {
1728
+						$success = $this->_insert_into_specific_table($table_obj, $fields_n_values,
1729
+							$main_table_pk_value);
1730
+						//if we died here, report the error
1731
+						if (! $success) {
1732
+							return false;
1733
+						}
1734
+					}
1735
+				}
1736
+			}
1737
+			//				//and now check that if we have cached any models by that ID on the model, that
1738
+			//				//they also get updated properly
1739
+			//				$model_object = $this->get_from_entity_map( $main_table_pk_value );
1740
+			//				if( $model_object ){
1741
+			//					foreach( $fields_n_values as $field => $value ){
1742
+			//						$model_object->set($field, $value);
1743
+			//let's make sure default_where strategy is followed now
1744
+			$this->_ignore_where_strategy = false;
1745
+		}
1746
+		//if we want to keep model objects in sync, AND
1747
+		//if this wasn't called from a model object (to update itself)
1748
+		//then we want to make sure we keep all the existing
1749
+		//model objects in sync with the db
1750
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1751
+			if ($this->has_primary_key_field()) {
1752
+				$model_objs_affected_ids = $this->get_col($query_params);
1753
+			} else {
1754
+				//we need to select a bunch of columns and then combine them into the the "index primary key string"s
1755
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1756
+				$model_objs_affected_ids = array();
1757
+				foreach ($models_affected_key_columns as $row) {
1758
+					$combined_index_key = $this->get_index_primary_key_string($row);
1759
+					$model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1760
+				}
1761
+			}
1762
+			if (! $model_objs_affected_ids) {
1763
+				//wait wait wait- if nothing was affected let's stop here
1764
+				return 0;
1765
+			}
1766
+			foreach ($model_objs_affected_ids as $id) {
1767
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1768
+				if ($model_obj_in_entity_map) {
1769
+					foreach ($fields_n_values as $field => $new_value) {
1770
+						$model_obj_in_entity_map->set($field, $new_value);
1771
+					}
1772
+				}
1773
+			}
1774
+			//if there is a primary key on this model, we can now do a slight optimization
1775
+			if ($this->has_primary_key_field()) {
1776
+				//we already know what we want to update. So let's make the query simpler so it's a little more efficient
1777
+				$query_params = array(
1778
+					array($this->primary_key_name() => array('IN', $model_objs_affected_ids)),
1779
+					'limit'                    => count($model_objs_affected_ids),
1780
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1781
+				);
1782
+			}
1783
+		}
1784
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1785
+		$SQL = "UPDATE "
1786
+			   . $model_query_info->get_full_join_sql()
1787
+			   . " SET "
1788
+			   . $this->_construct_update_sql($fields_n_values)
1789
+			   . $model_query_info->get_where_sql();//note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1790
+		$rows_affected = $this->_do_wpdb_query('query', array($SQL));
1791
+		/**
1792
+		 * Action called after a model update call has been made.
1793
+		 *
1794
+		 * @param EEM_Base $model
1795
+		 * @param array    $fields_n_values the updated fields and their new values
1796
+		 * @param array    $query_params    @see EEM_Base::get_all()
1797
+		 * @param int      $rows_affected
1798
+		 */
1799
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1800
+		return $rows_affected;//how many supposedly got updated
1801
+	}
1802
+
1803
+
1804
+
1805
+	/**
1806
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1807
+	 * are teh values of the field specified (or by default the primary key field)
1808
+	 * that matched the query params. Note that you should pass the name of the
1809
+	 * model FIELD, not the database table's column name.
1810
+	 *
1811
+	 * @param array  $query_params @see EEM_Base::get_all()
1812
+	 * @param string $field_to_select
1813
+	 * @return array just like $wpdb->get_col()
1814
+	 * @throws EE_Error
1815
+	 */
1816
+	public function get_col($query_params = array(), $field_to_select = null)
1817
+	{
1818
+		if ($field_to_select) {
1819
+			$field = $this->field_settings_for($field_to_select);
1820
+		} elseif ($this->has_primary_key_field()) {
1821
+			$field = $this->get_primary_key_field();
1822
+		} else {
1823
+			//no primary key, just grab the first column
1824
+			$field = reset($this->field_settings());
1825
+		}
1826
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1827
+		$select_expressions = $field->get_qualified_column();
1828
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1829
+		return $this->_do_wpdb_query('get_col', array($SQL));
1830
+	}
1831
+
1832
+
1833
+
1834
+	/**
1835
+	 * Returns a single column value for a single row from the database
1836
+	 *
1837
+	 * @param array  $query_params    @see EEM_Base::get_all()
1838
+	 * @param string $field_to_select @see EEM_Base::get_col()
1839
+	 * @return string
1840
+	 * @throws EE_Error
1841
+	 */
1842
+	public function get_var($query_params = array(), $field_to_select = null)
1843
+	{
1844
+		$query_params['limit'] = 1;
1845
+		$col = $this->get_col($query_params, $field_to_select);
1846
+		if (! empty($col)) {
1847
+			return reset($col);
1848
+		}
1849
+		return null;
1850
+	}
1851
+
1852
+
1853
+
1854
+	/**
1855
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1856
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1857
+	 * injection, but currently no further filtering is done
1858
+	 *
1859
+	 * @global      $wpdb
1860
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1861
+	 *                               be updated to in the DB
1862
+	 * @return string of SQL
1863
+	 * @throws EE_Error
1864
+	 */
1865
+	public function _construct_update_sql($fields_n_values)
1866
+	{
1867
+		/** @type WPDB $wpdb */
1868
+		global $wpdb;
1869
+		$cols_n_values = array();
1870
+		foreach ($fields_n_values as $field_name => $value) {
1871
+			$field_obj = $this->field_settings_for($field_name);
1872
+			//if the value is NULL, we want to assign the value to that.
1873
+			//wpdb->prepare doesn't really handle that properly
1874
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1875
+			$value_sql = $prepared_value === null ? 'NULL'
1876
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1877
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1878
+		}
1879
+		return implode(",", $cols_n_values);
1880
+	}
1881
+
1882
+
1883
+
1884
+	/**
1885
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1886
+	 * Performs a HARD delete, meaning the database row should always be removed,
1887
+	 * not just have a flag field on it switched
1888
+	 * Wrapper for EEM_Base::delete_permanently()
1889
+	 *
1890
+	 * @param mixed $id
1891
+	 * @param boolean $allow_blocking
1892
+	 * @return int the number of rows deleted
1893
+	 * @throws EE_Error
1894
+	 */
1895
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1896
+	{
1897
+		return $this->delete_permanently(
1898
+			array(
1899
+				array($this->get_primary_key_field()->get_name() => $id),
1900
+				'limit' => 1,
1901
+			),
1902
+			$allow_blocking
1903
+		);
1904
+	}
1905
+
1906
+
1907
+
1908
+	/**
1909
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1910
+	 * Wrapper for EEM_Base::delete()
1911
+	 *
1912
+	 * @param mixed $id
1913
+	 * @param boolean $allow_blocking
1914
+	 * @return int the number of rows deleted
1915
+	 * @throws EE_Error
1916
+	 */
1917
+	public function delete_by_ID($id, $allow_blocking = true)
1918
+	{
1919
+		return $this->delete(
1920
+			array(
1921
+				array($this->get_primary_key_field()->get_name() => $id),
1922
+				'limit' => 1,
1923
+			),
1924
+			$allow_blocking
1925
+		);
1926
+	}
1927
+
1928
+
1929
+
1930
+	/**
1931
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1932
+	 * meaning if the model has a field that indicates its been "trashed" or
1933
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1934
+	 *
1935
+	 * @see EEM_Base::delete_permanently
1936
+	 * @param array   $query_params
1937
+	 * @param boolean $allow_blocking
1938
+	 * @return int how many rows got deleted
1939
+	 * @throws EE_Error
1940
+	 */
1941
+	public function delete($query_params, $allow_blocking = true)
1942
+	{
1943
+		return $this->delete_permanently($query_params, $allow_blocking);
1944
+	}
1945
+
1946
+
1947
+
1948
+	/**
1949
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
1950
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1951
+	 * as archived, not actually deleted
1952
+	 *
1953
+	 * @param array   $query_params   very much like EEM_Base::get_all's $query_params
1954
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1955
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1956
+	 *                                deletes regardless of other objects which may depend on it. Its generally
1957
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1958
+	 *                                DB
1959
+	 * @return int how many rows got deleted
1960
+	 * @throws EE_Error
1961
+	 */
1962
+	public function delete_permanently($query_params, $allow_blocking = true)
1963
+	{
1964
+		/**
1965
+		 * Action called just before performing a real deletion query. You can use the
1966
+		 * model and its $query_params to find exactly which items will be deleted
1967
+		 *
1968
+		 * @param EEM_Base $model
1969
+		 * @param array    $query_params   @see EEM_Base::get_all()
1970
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
1971
+		 *                                 to block (prevent) this deletion
1972
+		 */
1973
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1974
+		//some MySQL databases may be running safe mode, which may restrict
1975
+		//deletion if there is no KEY column used in the WHERE statement of a deletion.
1976
+		//to get around this, we first do a SELECT, get all the IDs, and then run another query
1977
+		//to delete them
1978
+		$items_for_deletion = $this->_get_all_wpdb_results($query_params);
1979
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1980
+		$deletion_where_query_part = $this->_build_query_part_for_deleting_from_columns_and_values(
1981
+			$columns_and_ids_for_deleting
1982
+		);
1983
+		/**
1984
+		 * Allows client code to act on the items being deleted before the query is actually executed.
1985
+		 *
1986
+		 * @param EEM_Base $this  The model instance being acted on.
1987
+		 * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
1988
+		 * @param bool     $allow_blocking @see param description in method phpdoc block.
1989
+		 * @param array $columns_and_ids_for_deleting       An array indicating what entities will get removed as
1990
+		 *                                                  derived from the incoming query parameters.
1991
+		 *                                                  @see details on the structure of this array in the phpdocs
1992
+		 *                                                  for the `_get_ids_for_delete_method`
1993
+		 *
1994
+		 */
1995
+		do_action('AHEE__EEM_Base__delete__before_query',
1996
+			$this,
1997
+			$query_params,
1998
+			$allow_blocking,
1999
+			$columns_and_ids_for_deleting
2000
+		);
2001
+		if ($deletion_where_query_part) {
2002
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2003
+			$table_aliases = array_keys($this->_tables);
2004
+			$SQL = "DELETE "
2005
+				   . implode(", ", $table_aliases)
2006
+				   . " FROM "
2007
+				   . $model_query_info->get_full_join_sql()
2008
+				   . " WHERE "
2009
+				   . $deletion_where_query_part;
2010
+			$rows_deleted = $this->_do_wpdb_query('query', array($SQL));
2011
+		} else {
2012
+			$rows_deleted = 0;
2013
+		}
2014
+
2015
+		//Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2016
+		//there was no error with the delete query.
2017
+		if ($this->has_primary_key_field()
2018
+			&& $rows_deleted !== false
2019
+			&& isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2020
+		) {
2021
+			$ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2022
+			foreach ($ids_for_removal as $id) {
2023
+				if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2024
+					unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2025
+				}
2026
+			}
2027
+
2028
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2029
+			//`EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2030
+			//unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2031
+			// (although it is possible).
2032
+			//Note this can be skipped by using the provided filter and returning false.
2033
+			if (apply_filters(
2034
+				'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2035
+				! $this instanceof EEM_Extra_Meta,
2036
+				$this
2037
+			)) {
2038
+				EEM_Extra_Meta::instance()->delete_permanently(array(
2039
+					0 => array(
2040
+						'EXM_type' => $this->get_this_model_name(),
2041
+						'OBJ_ID'   => array(
2042
+							'IN',
2043
+							$ids_for_removal
2044
+						)
2045
+					)
2046
+				));
2047
+			}
2048
+		}
2049
+
2050
+		/**
2051
+		 * Action called just after performing a real deletion query. Although at this point the
2052
+		 * items should have been deleted
2053
+		 *
2054
+		 * @param EEM_Base $model
2055
+		 * @param array    $query_params @see EEM_Base::get_all()
2056
+		 * @param int      $rows_deleted
2057
+		 */
2058
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2059
+		return $rows_deleted;//how many supposedly got deleted
2060
+	}
2061
+
2062
+
2063
+
2064
+	/**
2065
+	 * Checks all the relations that throw error messages when there are blocking related objects
2066
+	 * for related model objects. If there are any related model objects on those relations,
2067
+	 * adds an EE_Error, and return true
2068
+	 *
2069
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2070
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2071
+	 *                                                 should be ignored when determining whether there are related
2072
+	 *                                                 model objects which block this model object's deletion. Useful
2073
+	 *                                                 if you know A is related to B and are considering deleting A,
2074
+	 *                                                 but want to see if A has any other objects blocking its deletion
2075
+	 *                                                 before removing the relation between A and B
2076
+	 * @return boolean
2077
+	 * @throws EE_Error
2078
+	 */
2079
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2080
+	{
2081
+		//first, if $ignore_this_model_obj was supplied, get its model
2082
+		if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2083
+			$ignored_model = $ignore_this_model_obj->get_model();
2084
+		} else {
2085
+			$ignored_model = null;
2086
+		}
2087
+		//now check all the relations of $this_model_obj_or_id and see if there
2088
+		//are any related model objects blocking it?
2089
+		$is_blocked = false;
2090
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2091
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2092
+				//if $ignore_this_model_obj was supplied, then for the query
2093
+				//on that model needs to be told to ignore $ignore_this_model_obj
2094
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2095
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, array(
2096
+						array(
2097
+							$ignored_model->get_primary_key_field()->get_name() => array(
2098
+								'!=',
2099
+								$ignore_this_model_obj->ID(),
2100
+							),
2101
+						),
2102
+					));
2103
+				} else {
2104
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2105
+				}
2106
+				if ($related_model_objects) {
2107
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2108
+					$is_blocked = true;
2109
+				}
2110
+			}
2111
+		}
2112
+		return $is_blocked;
2113
+	}
2114
+
2115
+
2116
+	/**
2117
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2118
+	 * @param array $row_results_for_deleting
2119
+	 * @param bool  $allow_blocking
2120
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2121
+	 *                 model DOES have a primary_key_field, then the array will be a simple single dimension array where
2122
+	 *                 the key is the fully qualified primary key column and the value is an array of ids that will be
2123
+	 *                 deleted. Example:
2124
+	 *                      array('Event.EVT_ID' => array( 1,2,3))
2125
+	 *                 If the model DOES NOT have a primary_key_field, then the array will be a two dimensional array
2126
+	 *                 where each element is a group of columns and values that get deleted. Example:
2127
+	 *                      array(
2128
+	 *                          0 => array(
2129
+	 *                              'Term_Relationship.object_id' => 1
2130
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2131
+	 *                          ),
2132
+	 *                          1 => array(
2133
+	 *                              'Term_Relationship.object_id' => 1
2134
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2135
+	 *                          )
2136
+	 *                      )
2137
+	 * @throws EE_Error
2138
+	 */
2139
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2140
+	{
2141
+		$ids_to_delete_indexed_by_column = array();
2142
+		if ($this->has_primary_key_field()) {
2143
+			$primary_table = $this->_get_main_table();
2144
+			$primary_table_pk_field = $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2145
+			$other_tables = $this->_get_other_tables();
2146
+			$ids_to_delete_indexed_by_column = $query = array();
2147
+			foreach ($row_results_for_deleting as $item_to_delete) {
2148
+				//before we mark this item for deletion,
2149
+				//make sure there's no related entities blocking its deletion (if we're checking)
2150
+				if (
2151
+					$allow_blocking
2152
+					&& $this->delete_is_blocked_by_related_models(
2153
+						$item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2154
+					)
2155
+				) {
2156
+					continue;
2157
+				}
2158
+				//primary table deletes
2159
+				if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2160
+					$ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2161
+						$item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2162
+				}
2163
+			}
2164
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2165
+			$fields = $this->get_combined_primary_key_fields();
2166
+			foreach ($row_results_for_deleting as $item_to_delete) {
2167
+				$ids_to_delete_indexed_by_column_for_row = array();
2168
+				foreach ($fields as $cpk_field) {
2169
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2170
+						$ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2171
+							$item_to_delete[$cpk_field->get_qualified_column()];
2172
+					}
2173
+				}
2174
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2175
+			}
2176
+		} else {
2177
+			//so there's no primary key and no combined key...
2178
+			//sorry, can't help you
2179
+			throw new EE_Error(
2180
+				sprintf(
2181
+					__(
2182
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2183
+						"event_espresso"
2184
+					), get_class($this)
2185
+				)
2186
+			);
2187
+		}
2188
+		return $ids_to_delete_indexed_by_column;
2189
+	}
2190
+
2191
+
2192
+	/**
2193
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2194
+	 * the corresponding query_part for the query performing the delete.
2195
+	 *
2196
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2197
+	 * @return string
2198
+	 * @throws EE_Error
2199
+	 */
2200
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column) {
2201
+		$query_part = '';
2202
+		if (empty($ids_to_delete_indexed_by_column)) {
2203
+			return $query_part;
2204
+		} elseif ($this->has_primary_key_field()) {
2205
+			$query = array();
2206
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2207
+				//make sure we have unique $ids
2208
+				$ids = array_unique($ids);
2209
+				$query[] = $column . ' IN(' . implode(',', $ids) . ')';
2210
+			}
2211
+			$query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2212
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2213
+			$ways_to_identify_a_row = array();
2214
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2215
+				$values_for_each_combined_primary_key_for_a_row = array();
2216
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2217
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2218
+				}
2219
+				$ways_to_identify_a_row[] = '('
2220
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2221
+											. ')';
2222
+			}
2223
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2224
+		}
2225
+		return $query_part;
2226
+	}
2227
+
2228
+
2229
+
2230
+	/**
2231
+	 * Gets the model field by the fully qualified name
2232
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2233
+	 * @return EE_Model_Field_Base
2234
+	 */
2235
+	public function get_field_by_column($qualified_column_name)
2236
+	{
2237
+	   foreach($this->field_settings(true) as $field_name => $field_obj){
2238
+		   if($field_obj->get_qualified_column() === $qualified_column_name){
2239
+			   return $field_obj;
2240
+		   }
2241
+	   }
2242
+		throw new EE_Error(
2243
+			sprintf(
2244
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2245
+				$this->get_this_model_name(),
2246
+				$qualified_column_name
2247
+			)
2248
+		);
2249
+	}
2250
+
2251
+
2252
+
2253
+	/**
2254
+	 * Count all the rows that match criteria expressed in $query_params (an array just like arg to EEM_Base::get_all).
2255
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2256
+	 * column
2257
+	 *
2258
+	 * @param array  $query_params   like EEM_Base::get_all's
2259
+	 * @param string $field_to_count field on model to count by (not column name)
2260
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2261
+	 *                               that by the setting $distinct to TRUE;
2262
+	 * @return int
2263
+	 * @throws EE_Error
2264
+	 */
2265
+	public function count($query_params = array(), $field_to_count = null, $distinct = false)
2266
+	{
2267
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2268
+		if ($field_to_count) {
2269
+			$field_obj = $this->field_settings_for($field_to_count);
2270
+			$column_to_count = $field_obj->get_qualified_column();
2271
+		} elseif ($this->has_primary_key_field()) {
2272
+			$pk_field_obj = $this->get_primary_key_field();
2273
+			$column_to_count = $pk_field_obj->get_qualified_column();
2274
+		} else {
2275
+			//there's no primary key
2276
+			//if we're counting distinct items, and there's no primary key,
2277
+			//we need to list out the columns for distinction;
2278
+			//otherwise we can just use star
2279
+			if ($distinct) {
2280
+				$columns_to_use = array();
2281
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2282
+					$columns_to_use[] = $field_obj->get_qualified_column();
2283
+				}
2284
+				$column_to_count = implode(',', $columns_to_use);
2285
+			} else {
2286
+				$column_to_count = '*';
2287
+			}
2288
+		}
2289
+		$column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2290
+		$SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2291
+		return (int)$this->_do_wpdb_query('get_var', array($SQL));
2292
+	}
2293
+
2294
+
2295
+
2296
+	/**
2297
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2298
+	 *
2299
+	 * @param array  $query_params like EEM_Base::get_all
2300
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2301
+	 * @return float
2302
+	 * @throws EE_Error
2303
+	 */
2304
+	public function sum($query_params, $field_to_sum = null)
2305
+	{
2306
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2307
+		if ($field_to_sum) {
2308
+			$field_obj = $this->field_settings_for($field_to_sum);
2309
+		} else {
2310
+			$field_obj = $this->get_primary_key_field();
2311
+		}
2312
+		$column_to_count = $field_obj->get_qualified_column();
2313
+		$SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2314
+		$return_value = $this->_do_wpdb_query('get_var', array($SQL));
2315
+		$data_type = $field_obj->get_wpdb_data_type();
2316
+		if ($data_type === '%d' || $data_type === '%s') {
2317
+			return (float)$return_value;
2318
+		}
2319
+		//must be %f
2320
+		return (float)$return_value;
2321
+	}
2322
+
2323
+
2324
+
2325
+	/**
2326
+	 * Just calls the specified method on $wpdb with the given arguments
2327
+	 * Consolidates a little extra error handling code
2328
+	 *
2329
+	 * @param string $wpdb_method
2330
+	 * @param array  $arguments_to_provide
2331
+	 * @throws EE_Error
2332
+	 * @global wpdb  $wpdb
2333
+	 * @return mixed
2334
+	 */
2335
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2336
+	{
2337
+		//if we're in maintenance mode level 2, DON'T run any queries
2338
+		//because level 2 indicates the database needs updating and
2339
+		//is probably out of sync with the code
2340
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2341
+			throw new EE_Error(sprintf(__("Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2342
+				"event_espresso")));
2343
+		}
2344
+		/** @type WPDB $wpdb */
2345
+		global $wpdb;
2346
+		if (! method_exists($wpdb, $wpdb_method)) {
2347
+			throw new EE_Error(sprintf(__('There is no method named "%s" on Wordpress\' $wpdb object',
2348
+				'event_espresso'), $wpdb_method));
2349
+		}
2350
+		if (WP_DEBUG) {
2351
+			$old_show_errors_value = $wpdb->show_errors;
2352
+			$wpdb->show_errors(false);
2353
+		}
2354
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2355
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2356
+		if (WP_DEBUG) {
2357
+			$wpdb->show_errors($old_show_errors_value);
2358
+			if (! empty($wpdb->last_error)) {
2359
+				throw new EE_Error(sprintf(__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2360
+			}
2361
+			if ($result === false) {
2362
+				throw new EE_Error(sprintf(__('WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2363
+					'event_espresso'), $wpdb_method, var_export($arguments_to_provide, true)));
2364
+			}
2365
+		} elseif ($result === false) {
2366
+			EE_Error::add_error(
2367
+				sprintf(
2368
+					__('A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2369
+						'event_espresso'),
2370
+					$wpdb_method,
2371
+					var_export($arguments_to_provide, true),
2372
+					$wpdb->last_error
2373
+				),
2374
+				__FILE__,
2375
+				__FUNCTION__,
2376
+				__LINE__
2377
+			);
2378
+		}
2379
+		return $result;
2380
+	}
2381
+
2382
+
2383
+
2384
+	/**
2385
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2386
+	 * and if there's an error tries to verify the DB is correct. Uses
2387
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2388
+	 * we should try to fix the EE core db, the addons, or just give up
2389
+	 *
2390
+	 * @param string $wpdb_method
2391
+	 * @param array  $arguments_to_provide
2392
+	 * @return mixed
2393
+	 */
2394
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2395
+	{
2396
+		/** @type WPDB $wpdb */
2397
+		global $wpdb;
2398
+		$wpdb->last_error = null;
2399
+		$result = call_user_func_array(array($wpdb, $wpdb_method), $arguments_to_provide);
2400
+		// was there an error running the query? but we don't care on new activations
2401
+		// (we're going to setup the DB anyway on new activations)
2402
+		if (($result === false || ! empty($wpdb->last_error))
2403
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2404
+		) {
2405
+			switch (EEM_Base::$_db_verification_level) {
2406
+				case EEM_Base::db_verified_none :
2407
+					// let's double-check core's DB
2408
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2409
+					break;
2410
+				case EEM_Base::db_verified_core :
2411
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2412
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2413
+					break;
2414
+				case EEM_Base::db_verified_addons :
2415
+					// ummmm... you in trouble
2416
+					return $result;
2417
+					break;
2418
+			}
2419
+			if (! empty($error_message)) {
2420
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2421
+				trigger_error($error_message);
2422
+			}
2423
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2424
+		}
2425
+		return $result;
2426
+	}
2427
+
2428
+
2429
+
2430
+	/**
2431
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2432
+	 * EEM_Base::$_db_verification_level
2433
+	 *
2434
+	 * @param string $wpdb_method
2435
+	 * @param array  $arguments_to_provide
2436
+	 * @return string
2437
+	 */
2438
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2439
+	{
2440
+		/** @type WPDB $wpdb */
2441
+		global $wpdb;
2442
+		//ok remember that we've already attempted fixing the core db, in case the problem persists
2443
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2444
+		$error_message = sprintf(
2445
+			__('WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2446
+				'event_espresso'),
2447
+			$wpdb->last_error,
2448
+			$wpdb_method,
2449
+			wp_json_encode($arguments_to_provide)
2450
+		);
2451
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2452
+		return $error_message;
2453
+	}
2454
+
2455
+
2456
+
2457
+	/**
2458
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2459
+	 * EEM_Base::$_db_verification_level
2460
+	 *
2461
+	 * @param $wpdb_method
2462
+	 * @param $arguments_to_provide
2463
+	 * @return string
2464
+	 */
2465
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2466
+	{
2467
+		/** @type WPDB $wpdb */
2468
+		global $wpdb;
2469
+		//ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2470
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2471
+		$error_message = sprintf(
2472
+			__('WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2473
+				'event_espresso'),
2474
+			$wpdb->last_error,
2475
+			$wpdb_method,
2476
+			wp_json_encode($arguments_to_provide)
2477
+		);
2478
+		EE_System::instance()->initialize_addons();
2479
+		return $error_message;
2480
+	}
2481
+
2482
+
2483
+
2484
+	/**
2485
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2486
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2487
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2488
+	 * ..."
2489
+	 *
2490
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2491
+	 * @return string
2492
+	 */
2493
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2494
+	{
2495
+		return " FROM " . $model_query_info->get_full_join_sql() .
2496
+			   $model_query_info->get_where_sql() .
2497
+			   $model_query_info->get_group_by_sql() .
2498
+			   $model_query_info->get_having_sql() .
2499
+			   $model_query_info->get_order_by_sql() .
2500
+			   $model_query_info->get_limit_sql();
2501
+	}
2502
+
2503
+
2504
+
2505
+	/**
2506
+	 * Set to easily debug the next X queries ran from this model.
2507
+	 *
2508
+	 * @param int $count
2509
+	 */
2510
+	public function show_next_x_db_queries($count = 1)
2511
+	{
2512
+		$this->_show_next_x_db_queries = $count;
2513
+	}
2514
+
2515
+
2516
+
2517
+	/**
2518
+	 * @param $sql_query
2519
+	 */
2520
+	public function show_db_query_if_previously_requested($sql_query)
2521
+	{
2522
+		if ($this->_show_next_x_db_queries > 0) {
2523
+			echo $sql_query;
2524
+			$this->_show_next_x_db_queries--;
2525
+		}
2526
+	}
2527
+
2528
+
2529
+
2530
+	/**
2531
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2532
+	 * There are the 3 cases:
2533
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2534
+	 * $otherModelObject has no ID, it is first saved.
2535
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2536
+	 * has no ID, it is first saved.
2537
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2538
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2539
+	 * join table
2540
+	 *
2541
+	 * @param        EE_Base_Class                     /int $thisModelObject
2542
+	 * @param        EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2543
+	 * @param string $relationName                     , key in EEM_Base::_relations
2544
+	 *                                                 an attendee to a group, you also want to specify which role they
2545
+	 *                                                 will have in that group. So you would use this parameter to
2546
+	 *                                                 specify array('role-column-name'=>'role-id')
2547
+	 * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2548
+	 *                                                 to for relation to methods that allow you to further specify
2549
+	 *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2550
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2551
+	 *                                                 because these will be inserted in any new rows created as well.
2552
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2553
+	 * @throws EE_Error
2554
+	 */
2555
+	public function add_relationship_to(
2556
+		$id_or_obj,
2557
+		$other_model_id_or_obj,
2558
+		$relationName,
2559
+		$extra_join_model_fields_n_values = array()
2560
+	) {
2561
+		$relation_obj = $this->related_settings_for($relationName);
2562
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2563
+	}
2564
+
2565
+
2566
+
2567
+	/**
2568
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2569
+	 * There are the 3 cases:
2570
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2571
+	 * error
2572
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2573
+	 * an error
2574
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2575
+	 *
2576
+	 * @param        EE_Base_Class /int $id_or_obj
2577
+	 * @param        EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2578
+	 * @param string $relationName key in EEM_Base::_relations
2579
+	 * @return boolean of success
2580
+	 * @throws EE_Error
2581
+	 * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2582
+	 *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2583
+	 *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2584
+	 *                             because these will be inserted in any new rows created as well.
2585
+	 */
2586
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = array())
2587
+	{
2588
+		$relation_obj = $this->related_settings_for($relationName);
2589
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2590
+	}
2591
+
2592
+
2593
+
2594
+	/**
2595
+	 * @param mixed           $id_or_obj
2596
+	 * @param string          $relationName
2597
+	 * @param array           $where_query_params
2598
+	 * @param EE_Base_Class[] objects to which relations were removed
2599
+	 * @return \EE_Base_Class[]
2600
+	 * @throws EE_Error
2601
+	 */
2602
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = array())
2603
+	{
2604
+		$relation_obj = $this->related_settings_for($relationName);
2605
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2606
+	}
2607
+
2608
+
2609
+
2610
+	/**
2611
+	 * Gets all the related items of the specified $model_name, using $query_params.
2612
+	 * Note: by default, we remove the "default query params"
2613
+	 * because we want to get even deleted items etc.
2614
+	 *
2615
+	 * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2616
+	 * @param string $model_name   like 'Event', 'Registration', etc. always singular
2617
+	 * @param array  $query_params like EEM_Base::get_all
2618
+	 * @return EE_Base_Class[]
2619
+	 * @throws EE_Error
2620
+	 */
2621
+	public function get_all_related($id_or_obj, $model_name, $query_params = null)
2622
+	{
2623
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2624
+		$relation_settings = $this->related_settings_for($model_name);
2625
+		return $relation_settings->get_all_related($model_obj, $query_params);
2626
+	}
2627
+
2628
+
2629
+
2630
+	/**
2631
+	 * Deletes all the model objects across the relation indicated by $model_name
2632
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2633
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2634
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2635
+	 *
2636
+	 * @param EE_Base_Class|int|string $id_or_obj
2637
+	 * @param string                   $model_name
2638
+	 * @param array                    $query_params
2639
+	 * @return int how many deleted
2640
+	 * @throws EE_Error
2641
+	 */
2642
+	public function delete_related($id_or_obj, $model_name, $query_params = array())
2643
+	{
2644
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2645
+		$relation_settings = $this->related_settings_for($model_name);
2646
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2647
+	}
2648
+
2649
+
2650
+
2651
+	/**
2652
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2653
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2654
+	 * the model objects can't be hard deleted because of blocking related model objects,
2655
+	 * just does a soft-delete on them instead.
2656
+	 *
2657
+	 * @param EE_Base_Class|int|string $id_or_obj
2658
+	 * @param string                   $model_name
2659
+	 * @param array                    $query_params
2660
+	 * @return int how many deleted
2661
+	 * @throws EE_Error
2662
+	 */
2663
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = array())
2664
+	{
2665
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2666
+		$relation_settings = $this->related_settings_for($model_name);
2667
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2668
+	}
2669
+
2670
+
2671
+
2672
+	/**
2673
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2674
+	 * unless otherwise specified in the $query_params
2675
+	 *
2676
+	 * @param        int             /EE_Base_Class $id_or_obj
2677
+	 * @param string $model_name     like 'Event', or 'Registration'
2678
+	 * @param array  $query_params   like EEM_Base::get_all's
2679
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2680
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2681
+	 *                               that by the setting $distinct to TRUE;
2682
+	 * @return int
2683
+	 * @throws EE_Error
2684
+	 */
2685
+	public function count_related(
2686
+		$id_or_obj,
2687
+		$model_name,
2688
+		$query_params = array(),
2689
+		$field_to_count = null,
2690
+		$distinct = false
2691
+	) {
2692
+		$related_model = $this->get_related_model_obj($model_name);
2693
+		//we're just going to use the query params on the related model's normal get_all query,
2694
+		//except add a condition to say to match the current mod
2695
+		if (! isset($query_params['default_where_conditions'])) {
2696
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2697
+		}
2698
+		$this_model_name = $this->get_this_model_name();
2699
+		$this_pk_field_name = $this->get_primary_key_field()->get_name();
2700
+		$query_params[0][$this_model_name . "." . $this_pk_field_name] = $id_or_obj;
2701
+		return $related_model->count($query_params, $field_to_count, $distinct);
2702
+	}
2703
+
2704
+
2705
+
2706
+	/**
2707
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2708
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2709
+	 *
2710
+	 * @param        int           /EE_Base_Class $id_or_obj
2711
+	 * @param string $model_name   like 'Event', or 'Registration'
2712
+	 * @param array  $query_params like EEM_Base::get_all's
2713
+	 * @param string $field_to_sum name of field to count by. By default, uses primary key
2714
+	 * @return float
2715
+	 * @throws EE_Error
2716
+	 */
2717
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2718
+	{
2719
+		$related_model = $this->get_related_model_obj($model_name);
2720
+		if (! is_array($query_params)) {
2721
+			EE_Error::doing_it_wrong('EEM_Base::sum_related',
2722
+				sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2723
+					gettype($query_params)), '4.6.0');
2724
+			$query_params = array();
2725
+		}
2726
+		//we're just going to use the query params on the related model's normal get_all query,
2727
+		//except add a condition to say to match the current mod
2728
+		if (! isset($query_params['default_where_conditions'])) {
2729
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2730
+		}
2731
+		$this_model_name = $this->get_this_model_name();
2732
+		$this_pk_field_name = $this->get_primary_key_field()->get_name();
2733
+		$query_params[0][$this_model_name . "." . $this_pk_field_name] = $id_or_obj;
2734
+		return $related_model->sum($query_params, $field_to_sum);
2735
+	}
2736
+
2737
+
2738
+
2739
+	/**
2740
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2741
+	 * $modelObject
2742
+	 *
2743
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2744
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2745
+	 * @param array               $query_params     like EEM_Base::get_all's
2746
+	 * @return EE_Base_Class
2747
+	 * @throws EE_Error
2748
+	 */
2749
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2750
+	{
2751
+		$query_params['limit'] = 1;
2752
+		$results = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2753
+		if ($results) {
2754
+			return array_shift($results);
2755
+		}
2756
+		return null;
2757
+	}
2758
+
2759
+
2760
+
2761
+	/**
2762
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2763
+	 *
2764
+	 * @return string
2765
+	 */
2766
+	public function get_this_model_name()
2767
+	{
2768
+		return str_replace("EEM_", "", get_class($this));
2769
+	}
2770
+
2771
+
2772
+
2773
+	/**
2774
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2775
+	 *
2776
+	 * @return EE_Any_Foreign_Model_Name_Field
2777
+	 * @throws EE_Error
2778
+	 */
2779
+	public function get_field_containing_related_model_name()
2780
+	{
2781
+		foreach ($this->field_settings(true) as $field) {
2782
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2783
+				$field_with_model_name = $field;
2784
+			}
2785
+		}
2786
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2787
+			throw new EE_Error(sprintf(__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2788
+				$this->get_this_model_name()));
2789
+		}
2790
+		return $field_with_model_name;
2791
+	}
2792
+
2793
+
2794
+
2795
+	/**
2796
+	 * Inserts a new entry into the database, for each table.
2797
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2798
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2799
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2800
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2801
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2802
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2803
+	 *
2804
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2805
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2806
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2807
+	 *                              of EEM_Base)
2808
+	 * @return int new primary key on main table that got inserted
2809
+	 * @throws EE_Error
2810
+	 */
2811
+	public function insert($field_n_values)
2812
+	{
2813
+		/**
2814
+		 * Filters the fields and their values before inserting an item using the models
2815
+		 *
2816
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2817
+		 * @param EEM_Base $model           the model used
2818
+		 */
2819
+		$field_n_values = (array)apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2820
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2821
+			$main_table = $this->_get_main_table();
2822
+			$new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2823
+			if ($new_id !== false) {
2824
+				foreach ($this->_get_other_tables() as $other_table) {
2825
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2826
+				}
2827
+			}
2828
+			/**
2829
+			 * Done just after attempting to insert a new model object
2830
+			 *
2831
+			 * @param EEM_Base   $model           used
2832
+			 * @param array      $fields_n_values fields and their values
2833
+			 * @param int|string the              ID of the newly-inserted model object
2834
+			 */
2835
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2836
+			return $new_id;
2837
+		}
2838
+		return false;
2839
+	}
2840
+
2841
+
2842
+
2843
+	/**
2844
+	 * Checks that the result would satisfy the unique indexes on this model
2845
+	 *
2846
+	 * @param array  $field_n_values
2847
+	 * @param string $action
2848
+	 * @return boolean
2849
+	 * @throws EE_Error
2850
+	 */
2851
+	protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2852
+	{
2853
+		foreach ($this->unique_indexes() as $index_name => $index) {
2854
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2855
+			if ($this->exists(array($uniqueness_where_params))) {
2856
+				EE_Error::add_error(
2857
+					sprintf(
2858
+						__(
2859
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2860
+							"event_espresso"
2861
+						),
2862
+						$action,
2863
+						$this->_get_class_name(),
2864
+						$index_name,
2865
+						implode(",", $index->field_names()),
2866
+						http_build_query($uniqueness_where_params)
2867
+					),
2868
+					__FILE__,
2869
+					__FUNCTION__,
2870
+					__LINE__
2871
+				);
2872
+				return false;
2873
+			}
2874
+		}
2875
+		return true;
2876
+	}
2877
+
2878
+
2879
+
2880
+	/**
2881
+	 * Checks the database for an item that conflicts (ie, if this item were
2882
+	 * saved to the DB would break some uniqueness requirement, like a primary key
2883
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
2884
+	 * can be either an EE_Base_Class or an array of fields n values
2885
+	 *
2886
+	 * @param EE_Base_Class|array $obj_or_fields_array
2887
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
2888
+	 *                                                 when looking for conflicts
2889
+	 *                                                 (ie, if false, we ignore the model object's primary key
2890
+	 *                                                 when finding "conflicts". If true, it's also considered).
2891
+	 *                                                 Only works for INT primary key,
2892
+	 *                                                 STRING primary keys cannot be ignored
2893
+	 * @throws EE_Error
2894
+	 * @return EE_Base_Class|array
2895
+	 */
2896
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2897
+	{
2898
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
2899
+			$fields_n_values = $obj_or_fields_array->model_field_array();
2900
+		} elseif (is_array($obj_or_fields_array)) {
2901
+			$fields_n_values = $obj_or_fields_array;
2902
+		} else {
2903
+			throw new EE_Error(
2904
+				sprintf(
2905
+					__(
2906
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2907
+						"event_espresso"
2908
+					),
2909
+					get_class($this),
2910
+					$obj_or_fields_array
2911
+				)
2912
+			);
2913
+		}
2914
+		$query_params = array();
2915
+		if ($this->has_primary_key_field()
2916
+			&& ($include_primary_key
2917
+				|| $this->get_primary_key_field()
2918
+				   instanceof
2919
+				   EE_Primary_Key_String_Field)
2920
+			&& isset($fields_n_values[$this->primary_key_name()])
2921
+		) {
2922
+			$query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
2923
+		}
2924
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2925
+			$uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2926
+			$query_params[0]['OR']['AND*' . $unique_index_name] = $uniqueness_where_params;
2927
+		}
2928
+		//if there is nothing to base this search on, then we shouldn't find anything
2929
+		if (empty($query_params)) {
2930
+			return array();
2931
+		}
2932
+		return $this->get_one($query_params);
2933
+	}
2934
+
2935
+
2936
+
2937
+	/**
2938
+	 * Like count, but is optimized and returns a boolean instead of an int
2939
+	 *
2940
+	 * @param array $query_params
2941
+	 * @return boolean
2942
+	 * @throws EE_Error
2943
+	 */
2944
+	public function exists($query_params)
2945
+	{
2946
+		$query_params['limit'] = 1;
2947
+		return $this->count($query_params) > 0;
2948
+	}
2949
+
2950
+
2951
+
2952
+	/**
2953
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
2954
+	 *
2955
+	 * @param int|string $id
2956
+	 * @return boolean
2957
+	 * @throws EE_Error
2958
+	 */
2959
+	public function exists_by_ID($id)
2960
+	{
2961
+		return $this->exists(
2962
+			array(
2963
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
2964
+				array(
2965
+					$this->primary_key_name() => $id,
2966
+				),
2967
+			)
2968
+		);
2969
+	}
2970
+
2971
+
2972
+
2973
+	/**
2974
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
2975
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
2976
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
2977
+	 * on the main table)
2978
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
2979
+	 * cases where we want to call it directly rather than via insert().
2980
+	 *
2981
+	 * @access   protected
2982
+	 * @param EE_Table_Base $table
2983
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
2984
+	 *                                       float
2985
+	 * @param int           $new_id          for now we assume only int keys
2986
+	 * @throws EE_Error
2987
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
2988
+	 * @return int ID of new row inserted, or FALSE on failure
2989
+	 */
2990
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
2991
+	{
2992
+		global $wpdb;
2993
+		$insertion_col_n_values = array();
2994
+		$format_for_insertion = array();
2995
+		$fields_on_table = $this->_get_fields_for_table($table->get_table_alias());
2996
+		foreach ($fields_on_table as $field_name => $field_obj) {
2997
+			//check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
2998
+			if ($field_obj->is_auto_increment()) {
2999
+				continue;
3000
+			}
3001
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3002
+			//if the value we want to assign it to is NULL, just don't mention it for the insertion
3003
+			if ($prepared_value !== null) {
3004
+				$insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3005
+				$format_for_insertion[] = $field_obj->get_wpdb_data_type();
3006
+			}
3007
+		}
3008
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3009
+			//its not the main table, so we should have already saved the main table's PK which we just inserted
3010
+			//so add the fk to the main table as a column
3011
+			$insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3012
+			$format_for_insertion[] = '%d';//yes right now we're only allowing these foreign keys to be INTs
3013
+		}
3014
+		//insert the new entry
3015
+		$result = $this->_do_wpdb_query('insert',
3016
+			array($table->get_table_name(), $insertion_col_n_values, $format_for_insertion));
3017
+		if ($result === false) {
3018
+			return false;
3019
+		}
3020
+		//ok, now what do we return for the ID of the newly-inserted thing?
3021
+		if ($this->has_primary_key_field()) {
3022
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3023
+				return $wpdb->insert_id;
3024
+			}
3025
+			//it's not an auto-increment primary key, so
3026
+			//it must have been supplied
3027
+			return $fields_n_values[$this->get_primary_key_field()->get_name()];
3028
+		}
3029
+		//we can't return a  primary key because there is none. instead return
3030
+		//a unique string indicating this model
3031
+		return $this->get_index_primary_key_string($fields_n_values);
3032
+	}
3033
+
3034
+
3035
+
3036
+	/**
3037
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3038
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3039
+	 * and there is no default, we pass it along. WPDB will take care of it)
3040
+	 *
3041
+	 * @param EE_Model_Field_Base $field_obj
3042
+	 * @param array               $fields_n_values
3043
+	 * @return mixed string|int|float depending on what the table column will be expecting
3044
+	 * @throws EE_Error
3045
+	 */
3046
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3047
+	{
3048
+		//if this field doesn't allow nullable, don't allow it
3049
+		if (
3050
+			! $field_obj->is_nullable()
3051
+			&& (
3052
+				! isset($fields_n_values[$field_obj->get_name()])
3053
+				|| $fields_n_values[$field_obj->get_name()] === null
3054
+			)
3055
+		) {
3056
+			$fields_n_values[$field_obj->get_name()] = $field_obj->get_default_value();
3057
+		}
3058
+		$unprepared_value = isset($fields_n_values[$field_obj->get_name()])
3059
+			? $fields_n_values[$field_obj->get_name()]
3060
+			: null;
3061
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3062
+	}
3063
+
3064
+
3065
+
3066
+	/**
3067
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3068
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3069
+	 * the field's prepare_for_set() method.
3070
+	 *
3071
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3072
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3073
+	 *                                   top of file)
3074
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3075
+	 *                                   $value is a custom selection
3076
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3077
+	 */
3078
+	private function _prepare_value_for_use_in_db($value, $field)
3079
+	{
3080
+		if ($field && $field instanceof EE_Model_Field_Base) {
3081
+			switch ($this->_values_already_prepared_by_model_object) {
3082
+				/** @noinspection PhpMissingBreakStatementInspection */
3083
+				case self::not_prepared_by_model_object:
3084
+					$value = $field->prepare_for_set($value);
3085
+				//purposefully left out "return"
3086
+				case self::prepared_by_model_object:
3087
+					/** @noinspection SuspiciousAssignmentsInspection */
3088
+					$value = $field->prepare_for_use_in_db($value);
3089
+				case self::prepared_for_use_in_db:
3090
+					//leave the value alone
3091
+			}
3092
+			return $value;
3093
+		}
3094
+		return $value;
3095
+	}
3096
+
3097
+
3098
+
3099
+	/**
3100
+	 * Returns the main table on this model
3101
+	 *
3102
+	 * @return EE_Primary_Table
3103
+	 * @throws EE_Error
3104
+	 */
3105
+	protected function _get_main_table()
3106
+	{
3107
+		foreach ($this->_tables as $table) {
3108
+			if ($table instanceof EE_Primary_Table) {
3109
+				return $table;
3110
+			}
3111
+		}
3112
+		throw new EE_Error(sprintf(__('There are no main tables on %s. They should be added to _tables array in the constructor',
3113
+			'event_espresso'), get_class($this)));
3114
+	}
3115
+
3116
+
3117
+
3118
+	/**
3119
+	 * table
3120
+	 * returns EE_Primary_Table table name
3121
+	 *
3122
+	 * @return string
3123
+	 * @throws EE_Error
3124
+	 */
3125
+	public function table()
3126
+	{
3127
+		return $this->_get_main_table()->get_table_name();
3128
+	}
3129
+
3130
+
3131
+
3132
+	/**
3133
+	 * table
3134
+	 * returns first EE_Secondary_Table table name
3135
+	 *
3136
+	 * @return string
3137
+	 */
3138
+	public function second_table()
3139
+	{
3140
+		// grab second table from tables array
3141
+		$second_table = end($this->_tables);
3142
+		return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3143
+	}
3144
+
3145
+
3146
+
3147
+	/**
3148
+	 * get_table_obj_by_alias
3149
+	 * returns table name given it's alias
3150
+	 *
3151
+	 * @param string $table_alias
3152
+	 * @return EE_Primary_Table | EE_Secondary_Table
3153
+	 */
3154
+	public function get_table_obj_by_alias($table_alias = '')
3155
+	{
3156
+		return isset($this->_tables[$table_alias]) ? $this->_tables[$table_alias] : null;
3157
+	}
3158
+
3159
+
3160
+
3161
+	/**
3162
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3163
+	 *
3164
+	 * @return EE_Secondary_Table[]
3165
+	 */
3166
+	protected function _get_other_tables()
3167
+	{
3168
+		$other_tables = array();
3169
+		foreach ($this->_tables as $table_alias => $table) {
3170
+			if ($table instanceof EE_Secondary_Table) {
3171
+				$other_tables[$table_alias] = $table;
3172
+			}
3173
+		}
3174
+		return $other_tables;
3175
+	}
3176
+
3177
+
3178
+
3179
+	/**
3180
+	 * Finds all the fields that correspond to the given table
3181
+	 *
3182
+	 * @param string $table_alias , array key in EEM_Base::_tables
3183
+	 * @return EE_Model_Field_Base[]
3184
+	 */
3185
+	public function _get_fields_for_table($table_alias)
3186
+	{
3187
+		return $this->_fields[$table_alias];
3188
+	}
3189
+
3190
+
3191
+
3192
+	/**
3193
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3194
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3195
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3196
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3197
+	 * related Registration, Transaction, and Payment models.
3198
+	 *
3199
+	 * @param array $query_params like EEM_Base::get_all's $query_parameters['where']
3200
+	 * @return EE_Model_Query_Info_Carrier
3201
+	 * @throws EE_Error
3202
+	 */
3203
+	public function _extract_related_models_from_query($query_params)
3204
+	{
3205
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3206
+		if (array_key_exists(0, $query_params)) {
3207
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3208
+		}
3209
+		if (array_key_exists('group_by', $query_params)) {
3210
+			if (is_array($query_params['group_by'])) {
3211
+				$this->_extract_related_models_from_sub_params_array_values(
3212
+					$query_params['group_by'],
3213
+					$query_info_carrier,
3214
+					'group_by'
3215
+				);
3216
+			} elseif (! empty ($query_params['group_by'])) {
3217
+				$this->_extract_related_model_info_from_query_param(
3218
+					$query_params['group_by'],
3219
+					$query_info_carrier,
3220
+					'group_by'
3221
+				);
3222
+			}
3223
+		}
3224
+		if (array_key_exists('having', $query_params)) {
3225
+			$this->_extract_related_models_from_sub_params_array_keys(
3226
+				$query_params[0],
3227
+				$query_info_carrier,
3228
+				'having'
3229
+			);
3230
+		}
3231
+		if (array_key_exists('order_by', $query_params)) {
3232
+			if (is_array($query_params['order_by'])) {
3233
+				$this->_extract_related_models_from_sub_params_array_keys(
3234
+					$query_params['order_by'],
3235
+					$query_info_carrier,
3236
+					'order_by'
3237
+				);
3238
+			} elseif (! empty($query_params['order_by'])) {
3239
+				$this->_extract_related_model_info_from_query_param(
3240
+					$query_params['order_by'],
3241
+					$query_info_carrier,
3242
+					'order_by'
3243
+				);
3244
+			}
3245
+		}
3246
+		if (array_key_exists('force_join', $query_params)) {
3247
+			$this->_extract_related_models_from_sub_params_array_values(
3248
+				$query_params['force_join'],
3249
+				$query_info_carrier,
3250
+				'force_join'
3251
+			);
3252
+		}
3253
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3254
+		return $query_info_carrier;
3255
+	}
3256
+
3257
+
3258
+
3259
+	/**
3260
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3261
+	 *
3262
+	 * @param array                       $sub_query_params like EEM_Base::get_all's $query_params[0] or
3263
+	 *                                                      $query_params['having']
3264
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3265
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3266
+	 * @throws EE_Error
3267
+	 * @return \EE_Model_Query_Info_Carrier
3268
+	 */
3269
+	private function _extract_related_models_from_sub_params_array_keys(
3270
+		$sub_query_params,
3271
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3272
+		$query_param_type
3273
+	) {
3274
+		if (! empty($sub_query_params)) {
3275
+			$sub_query_params = (array)$sub_query_params;
3276
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3277
+				//$param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3278
+				$this->_extract_related_model_info_from_query_param($param, $model_query_info_carrier,
3279
+					$query_param_type);
3280
+				//if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3281
+				//indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3282
+				//extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3283
+				//of array('Registration.TXN_ID'=>23)
3284
+				$query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3285
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3286
+					if (! is_array($possibly_array_of_params)) {
3287
+						throw new EE_Error(sprintf(__("You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3288
+							"event_espresso"),
3289
+							$param, $possibly_array_of_params));
3290
+					}
3291
+					$this->_extract_related_models_from_sub_params_array_keys(
3292
+						$possibly_array_of_params,
3293
+						$model_query_info_carrier, $query_param_type
3294
+					);
3295
+				} elseif ($query_param_type === 0 //ie WHERE
3296
+						  && is_array($possibly_array_of_params)
3297
+						  && isset($possibly_array_of_params[2])
3298
+						  && $possibly_array_of_params[2] == true
3299
+				) {
3300
+					//then $possible_array_of_params looks something like array('<','DTT_sold',true)
3301
+					//indicating that $possible_array_of_params[1] is actually a field name,
3302
+					//from which we should extract query parameters!
3303
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3304
+						throw new EE_Error(sprintf(__("Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3305
+							"event_espresso"), $query_param_type, implode(",", $possibly_array_of_params)));
3306
+					}
3307
+					$this->_extract_related_model_info_from_query_param($possibly_array_of_params[1],
3308
+						$model_query_info_carrier, $query_param_type);
3309
+				}
3310
+			}
3311
+		}
3312
+		return $model_query_info_carrier;
3313
+	}
3314
+
3315
+
3316
+
3317
+	/**
3318
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3319
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3320
+	 *
3321
+	 * @param array                       $sub_query_params like EEM_Base::get_all's $query_params[0] or
3322
+	 *                                                      $query_params['having']
3323
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3324
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3325
+	 * @throws EE_Error
3326
+	 * @return \EE_Model_Query_Info_Carrier
3327
+	 */
3328
+	private function _extract_related_models_from_sub_params_array_values(
3329
+		$sub_query_params,
3330
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3331
+		$query_param_type
3332
+	) {
3333
+		if (! empty($sub_query_params)) {
3334
+			if (! is_array($sub_query_params)) {
3335
+				throw new EE_Error(sprintf(__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3336
+					$sub_query_params));
3337
+			}
3338
+			foreach ($sub_query_params as $param) {
3339
+				//$param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3340
+				$this->_extract_related_model_info_from_query_param($param, $model_query_info_carrier,
3341
+					$query_param_type);
3342
+			}
3343
+		}
3344
+		return $model_query_info_carrier;
3345
+	}
3346
+
3347
+
3348
+
3349
+	/**
3350
+	 * Extract all the query parts from $query_params (an array like whats passed to EEM_Base::get_all)
3351
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3352
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3353
+	 * but use them in a different order. Eg, we need to know what models we are querying
3354
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3355
+	 * other models before we can finalize the where clause SQL.
3356
+	 *
3357
+	 * @param array $query_params
3358
+	 * @throws EE_Error
3359
+	 * @return EE_Model_Query_Info_Carrier
3360
+	 */
3361
+	public function _create_model_query_info_carrier($query_params)
3362
+	{
3363
+		if (! is_array($query_params)) {
3364
+			EE_Error::doing_it_wrong(
3365
+				'EEM_Base::_create_model_query_info_carrier',
3366
+				sprintf(
3367
+					__(
3368
+						'$query_params should be an array, you passed a variable of type %s',
3369
+						'event_espresso'
3370
+					),
3371
+					gettype($query_params)
3372
+				),
3373
+				'4.6.0'
3374
+			);
3375
+			$query_params = array();
3376
+		}
3377
+		$where_query_params = isset($query_params[0]) ? $query_params[0] : array();
3378
+		//first check if we should alter the query to account for caps or not
3379
+		//because the caps might require us to do extra joins
3380
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3381
+			$query_params[0] = $where_query_params = array_replace_recursive(
3382
+				$where_query_params,
3383
+				$this->caps_where_conditions(
3384
+					$query_params['caps']
3385
+				)
3386
+			);
3387
+		}
3388
+		$query_object = $this->_extract_related_models_from_query($query_params);
3389
+		//verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3390
+		foreach ($where_query_params as $key => $value) {
3391
+			if (is_int($key)) {
3392
+				throw new EE_Error(
3393
+					sprintf(
3394
+						__(
3395
+							"WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3396
+							"event_espresso"
3397
+						),
3398
+						$key,
3399
+						var_export($value, true),
3400
+						var_export($query_params, true),
3401
+						get_class($this)
3402
+					)
3403
+				);
3404
+			}
3405
+		}
3406
+		if (
3407
+			array_key_exists('default_where_conditions', $query_params)
3408
+			&& ! empty($query_params['default_where_conditions'])
3409
+		) {
3410
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3411
+		} else {
3412
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3413
+		}
3414
+		$where_query_params = array_merge(
3415
+			$this->_get_default_where_conditions_for_models_in_query(
3416
+				$query_object,
3417
+				$use_default_where_conditions,
3418
+				$where_query_params
3419
+			),
3420
+			$where_query_params
3421
+		);
3422
+		$query_object->set_where_sql($this->_construct_where_clause($where_query_params));
3423
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3424
+		// So we need to setup a subquery and use that for the main join.
3425
+		// Note for now this only works on the primary table for the model.
3426
+		// So for instance, you could set the limit array like this:
3427
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3428
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3429
+			$query_object->set_main_model_join_sql(
3430
+				$this->_construct_limit_join_select(
3431
+					$query_params['on_join_limit'][0],
3432
+					$query_params['on_join_limit'][1]
3433
+				)
3434
+			);
3435
+		}
3436
+		//set limit
3437
+		if (array_key_exists('limit', $query_params)) {
3438
+			if (is_array($query_params['limit'])) {
3439
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3440
+					$e = sprintf(
3441
+						__(
3442
+							"Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3443
+							"event_espresso"
3444
+						),
3445
+						http_build_query($query_params['limit'])
3446
+					);
3447
+					throw new EE_Error($e . "|" . $e);
3448
+				}
3449
+				//they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3450
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3451
+			} elseif (! empty ($query_params['limit'])) {
3452
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3453
+			}
3454
+		}
3455
+		//set order by
3456
+		if (array_key_exists('order_by', $query_params)) {
3457
+			if (is_array($query_params['order_by'])) {
3458
+				//if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3459
+				//specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3460
+				//including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3461
+				if (array_key_exists('order', $query_params)) {
3462
+					throw new EE_Error(
3463
+						sprintf(
3464
+							__(
3465
+								"In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3466
+								"event_espresso"
3467
+							),
3468
+							get_class($this),
3469
+							implode(", ", array_keys($query_params['order_by'])),
3470
+							implode(", ", $query_params['order_by']),
3471
+							$query_params['order']
3472
+						)
3473
+					);
3474
+				}
3475
+				$this->_extract_related_models_from_sub_params_array_keys(
3476
+					$query_params['order_by'],
3477
+					$query_object,
3478
+					'order_by'
3479
+				);
3480
+				//assume it's an array of fields to order by
3481
+				$order_array = array();
3482
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3483
+					$order = $this->_extract_order($order);
3484
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3485
+				}
3486
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3487
+			} elseif (! empty ($query_params['order_by'])) {
3488
+				$this->_extract_related_model_info_from_query_param(
3489
+					$query_params['order_by'],
3490
+					$query_object,
3491
+					'order',
3492
+					$query_params['order_by']
3493
+				);
3494
+				$order = isset($query_params['order'])
3495
+					? $this->_extract_order($query_params['order'])
3496
+					: 'DESC';
3497
+				$query_object->set_order_by_sql(
3498
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3499
+				);
3500
+			}
3501
+		}
3502
+		//if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3503
+		if (! array_key_exists('order_by', $query_params)
3504
+			&& array_key_exists('order', $query_params)
3505
+			&& ! empty($query_params['order'])
3506
+		) {
3507
+			$pk_field = $this->get_primary_key_field();
3508
+			$order = $this->_extract_order($query_params['order']);
3509
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3510
+		}
3511
+		//set group by
3512
+		if (array_key_exists('group_by', $query_params)) {
3513
+			if (is_array($query_params['group_by'])) {
3514
+				//it's an array, so assume we'll be grouping by a bunch of stuff
3515
+				$group_by_array = array();
3516
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3517
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3518
+				}
3519
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3520
+			} elseif (! empty ($query_params['group_by'])) {
3521
+				$query_object->set_group_by_sql(
3522
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3523
+				);
3524
+			}
3525
+		}
3526
+		//set having
3527
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3528
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3529
+		}
3530
+		//now, just verify they didn't pass anything wack
3531
+		foreach ($query_params as $query_key => $query_value) {
3532
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3533
+				throw new EE_Error(
3534
+					sprintf(
3535
+						__(
3536
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3537
+							'event_espresso'
3538
+						),
3539
+						$query_key,
3540
+						get_class($this),
3541
+						//						print_r( $this->_allowed_query_params, TRUE )
3542
+						implode(',', $this->_allowed_query_params)
3543
+					)
3544
+				);
3545
+			}
3546
+		}
3547
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3548
+		if (empty($main_model_join_sql)) {
3549
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3550
+		}
3551
+		return $query_object;
3552
+	}
3553
+
3554
+
3555
+
3556
+	/**
3557
+	 * Gets the where conditions that should be imposed on the query based on the
3558
+	 * context (eg reading frontend, backend, edit or delete).
3559
+	 *
3560
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3561
+	 * @return array like EEM_Base::get_all() 's $query_params[0]
3562
+	 * @throws EE_Error
3563
+	 */
3564
+	public function caps_where_conditions($context = self::caps_read)
3565
+	{
3566
+		EEM_Base::verify_is_valid_cap_context($context);
3567
+		$cap_where_conditions = array();
3568
+		$cap_restrictions = $this->caps_missing($context);
3569
+		/**
3570
+		 * @var $cap_restrictions EE_Default_Where_Conditions[]
3571
+		 */
3572
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3573
+			$cap_where_conditions = array_replace_recursive($cap_where_conditions,
3574
+				$restriction_if_no_cap->get_default_where_conditions());
3575
+		}
3576
+		return apply_filters('FHEE__EEM_Base__caps_where_conditions__return', $cap_where_conditions, $this, $context,
3577
+			$cap_restrictions);
3578
+	}
3579
+
3580
+
3581
+
3582
+	/**
3583
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3584
+	 * otherwise throws an exception
3585
+	 *
3586
+	 * @param string $should_be_order_string
3587
+	 * @return string either ASC, asc, DESC or desc
3588
+	 * @throws EE_Error
3589
+	 */
3590
+	private function _extract_order($should_be_order_string)
3591
+	{
3592
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3593
+			return $should_be_order_string;
3594
+		}
3595
+		throw new EE_Error(
3596
+			sprintf(
3597
+				__(
3598
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3599
+					"event_espresso"
3600
+				), get_class($this), $should_be_order_string
3601
+			)
3602
+		);
3603
+	}
3604
+
3605
+
3606
+
3607
+	/**
3608
+	 * Looks at all the models which are included in this query, and asks each
3609
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3610
+	 * so they can be merged
3611
+	 *
3612
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3613
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3614
+	 *                                                                  'none' means NO default where conditions will
3615
+	 *                                                                  be used AT ALL during this query.
3616
+	 *                                                                  'other_models_only' means default where
3617
+	 *                                                                  conditions from other models will be used, but
3618
+	 *                                                                  not for this primary model. 'all', the default,
3619
+	 *                                                                  means default where conditions will apply as
3620
+	 *                                                                  normal
3621
+	 * @param array                       $where_query_params           like EEM_Base::get_all's $query_params[0]
3622
+	 * @throws EE_Error
3623
+	 * @return array like $query_params[0], see EEM_Base::get_all for documentation
3624
+	 */
3625
+	private function _get_default_where_conditions_for_models_in_query(
3626
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3627
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3628
+		$where_query_params = array()
3629
+	) {
3630
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3631
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3632
+			throw new EE_Error(sprintf(__("You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3633
+				"event_espresso"), $use_default_where_conditions,
3634
+				implode(", ", $allowed_used_default_where_conditions_values)));
3635
+		}
3636
+		$universal_query_params = array();
3637
+		if ($this->_should_use_default_where_conditions( $use_default_where_conditions, true)) {
3638
+			$universal_query_params = $this->_get_default_where_conditions();
3639
+		} else if ($this->_should_use_minimum_where_conditions( $use_default_where_conditions, true)) {
3640
+			$universal_query_params = $this->_get_minimum_where_conditions();
3641
+		}
3642
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3643
+			$related_model = $this->get_related_model_obj($model_name);
3644
+			if ( $this->_should_use_default_where_conditions( $use_default_where_conditions, false)) {
3645
+				$related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3646
+			} elseif ($this->_should_use_minimum_where_conditions( $use_default_where_conditions, false)) {
3647
+				$related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3648
+			} else {
3649
+				//we don't want to add full or even minimum default where conditions from this model, so just continue
3650
+				continue;
3651
+			}
3652
+			$overrides = $this->_override_defaults_or_make_null_friendly(
3653
+				$related_model_universal_where_params,
3654
+				$where_query_params,
3655
+				$related_model,
3656
+				$model_relation_path
3657
+			);
3658
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3659
+				$universal_query_params,
3660
+				$overrides
3661
+			);
3662
+		}
3663
+		return $universal_query_params;
3664
+	}
3665
+
3666
+
3667
+
3668
+	/**
3669
+	 * Determines whether or not we should use default where conditions for the model in question
3670
+	 * (this model, or other related models).
3671
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3672
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3673
+	 * We should use default where conditions on related models when they requested to use default where conditions
3674
+	 * on all models, or specifically just on other related models
3675
+	 * @param      $default_where_conditions_value
3676
+	 * @param bool $for_this_model false means this is for OTHER related models
3677
+	 * @return bool
3678
+	 */
3679
+	private function _should_use_default_where_conditions( $default_where_conditions_value, $for_this_model = true )
3680
+	{
3681
+		return (
3682
+				   $for_this_model
3683
+				   && in_array(
3684
+					   $default_where_conditions_value,
3685
+					   array(
3686
+						   EEM_Base::default_where_conditions_all,
3687
+						   EEM_Base::default_where_conditions_this_only,
3688
+						   EEM_Base::default_where_conditions_minimum_others,
3689
+					   ),
3690
+					   true
3691
+				   )
3692
+			   )
3693
+			   || (
3694
+				   ! $for_this_model
3695
+				   && in_array(
3696
+					   $default_where_conditions_value,
3697
+					   array(
3698
+						   EEM_Base::default_where_conditions_all,
3699
+						   EEM_Base::default_where_conditions_others_only,
3700
+					   ),
3701
+					   true
3702
+				   )
3703
+			   );
3704
+	}
3705
+
3706
+	/**
3707
+	 * Determines whether or not we should use default minimum conditions for the model in question
3708
+	 * (this model, or other related models).
3709
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3710
+	 * where conditions.
3711
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3712
+	 * on this model or others
3713
+	 * @param      $default_where_conditions_value
3714
+	 * @param bool $for_this_model false means this is for OTHER related models
3715
+	 * @return bool
3716
+	 */
3717
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3718
+	{
3719
+		return (
3720
+				   $for_this_model
3721
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3722
+			   )
3723
+			   || (
3724
+				   ! $for_this_model
3725
+				   && in_array(
3726
+					   $default_where_conditions_value,
3727
+					   array(
3728
+						   EEM_Base::default_where_conditions_minimum_others,
3729
+						   EEM_Base::default_where_conditions_minimum_all,
3730
+					   ),
3731
+					   true
3732
+				   )
3733
+			   );
3734
+	}
3735
+
3736
+
3737
+	/**
3738
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3739
+	 * then we also add a special where condition which allows for that model's primary key
3740
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3741
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3742
+	 *
3743
+	 * @param array    $default_where_conditions
3744
+	 * @param array    $provided_where_conditions
3745
+	 * @param EEM_Base $model
3746
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3747
+	 * @return array like EEM_Base::get_all's $query_params[0]
3748
+	 * @throws EE_Error
3749
+	 */
3750
+	private function _override_defaults_or_make_null_friendly(
3751
+		$default_where_conditions,
3752
+		$provided_where_conditions,
3753
+		$model,
3754
+		$model_relation_path
3755
+	) {
3756
+		$null_friendly_where_conditions = array();
3757
+		$none_overridden = true;
3758
+		$or_condition_key_for_defaults = 'OR*' . get_class($model);
3759
+		foreach ($default_where_conditions as $key => $val) {
3760
+			if (isset($provided_where_conditions[$key])) {
3761
+				$none_overridden = false;
3762
+			} else {
3763
+				$null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3764
+			}
3765
+		}
3766
+		if ($none_overridden && $default_where_conditions) {
3767
+			if ($model->has_primary_key_field()) {
3768
+				$null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3769
+																				. "."
3770
+																				. $model->primary_key_name()] = array('IS NULL');
3771
+			}/*else{
3772 3772
 				//@todo NO PK, use other defaults
3773 3773
 			}*/
3774
-        }
3775
-        return $null_friendly_where_conditions;
3776
-    }
3777
-
3778
-
3779
-
3780
-    /**
3781
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3782
-     * default where conditions on all get_all, update, and delete queries done by this model.
3783
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3784
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3785
-     *
3786
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3787
-     * @return array like EEM_Base::get_all's $query_params[0] (where conditions)
3788
-     */
3789
-    private function _get_default_where_conditions($model_relation_path = null)
3790
-    {
3791
-        if ($this->_ignore_where_strategy) {
3792
-            return array();
3793
-        }
3794
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3795
-    }
3796
-
3797
-
3798
-
3799
-    /**
3800
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3801
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3802
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3803
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3804
-     * Similar to _get_default_where_conditions
3805
-     *
3806
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3807
-     * @return array like EEM_Base::get_all's $query_params[0] (where conditions)
3808
-     */
3809
-    protected function _get_minimum_where_conditions($model_relation_path = null)
3810
-    {
3811
-        if ($this->_ignore_where_strategy) {
3812
-            return array();
3813
-        }
3814
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3815
-    }
3816
-
3817
-
3818
-
3819
-    /**
3820
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3821
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3822
-     *
3823
-     * @param EE_Model_Query_Info_Carrier $model_query_info
3824
-     * @return string
3825
-     * @throws EE_Error
3826
-     */
3827
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3828
-    {
3829
-        $selects = $this->_get_columns_to_select_for_this_model();
3830
-        foreach (
3831
-            $model_query_info->get_model_names_included() as $model_relation_chain =>
3832
-            $name_of_other_model_included
3833
-        ) {
3834
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3835
-            $other_model_selects = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3836
-            foreach ($other_model_selects as $key => $value) {
3837
-                $selects[] = $value;
3838
-            }
3839
-        }
3840
-        return implode(", ", $selects);
3841
-    }
3842
-
3843
-
3844
-
3845
-    /**
3846
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3847
-     * So that's going to be the columns for all the fields on the model
3848
-     *
3849
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
3850
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3851
-     */
3852
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3853
-    {
3854
-        $fields = $this->field_settings();
3855
-        $selects = array();
3856
-        $table_alias_with_model_relation_chain_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_prefix($model_relation_chain,
3857
-            $this->get_this_model_name());
3858
-        foreach ($fields as $field_obj) {
3859
-            $selects[] = $table_alias_with_model_relation_chain_prefix
3860
-                         . $field_obj->get_table_alias()
3861
-                         . "."
3862
-                         . $field_obj->get_table_column()
3863
-                         . " AS '"
3864
-                         . $table_alias_with_model_relation_chain_prefix
3865
-                         . $field_obj->get_table_alias()
3866
-                         . "."
3867
-                         . $field_obj->get_table_column()
3868
-                         . "'";
3869
-        }
3870
-        //make sure we are also getting the PKs of each table
3871
-        $tables = $this->get_tables();
3872
-        if (count($tables) > 1) {
3873
-            foreach ($tables as $table_obj) {
3874
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3875
-                                       . $table_obj->get_fully_qualified_pk_column();
3876
-                if (! in_array($qualified_pk_column, $selects)) {
3877
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3878
-                }
3879
-            }
3880
-        }
3881
-        return $selects;
3882
-    }
3883
-
3884
-
3885
-
3886
-    /**
3887
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3888
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3889
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3890
-     * SQL for joining, and the data types
3891
-     *
3892
-     * @param null|string                 $original_query_param
3893
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
3894
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3895
-     * @param    string                   $query_param_type     like Registration.Transaction.TXN_ID
3896
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3897
-     *                                                          column name. We only want model names, eg 'Event.Venue'
3898
-     *                                                          or 'Registration's
3899
-     * @param string                      $original_query_param what it originally was (eg
3900
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3901
-     *                                                          matches $query_param
3902
-     * @throws EE_Error
3903
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3904
-     */
3905
-    private function _extract_related_model_info_from_query_param(
3906
-        $query_param,
3907
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
3908
-        $query_param_type,
3909
-        $original_query_param = null
3910
-    ) {
3911
-        if ($original_query_param === null) {
3912
-            $original_query_param = $query_param;
3913
-        }
3914
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
3915
-        /** @var $allow_logic_query_params bool whether or not to allow logic_query_params like 'NOT','OR', or 'AND' */
3916
-        $allow_logic_query_params = in_array($query_param_type, array('where', 'having', 0, 'custom_selects'), true);
3917
-        $allow_fields = in_array(
3918
-            $query_param_type,
3919
-            array('where', 'having', 'order_by', 'group_by', 'order', 'custom_selects', 0),
3920
-            true
3921
-        );
3922
-        //check to see if we have a field on this model
3923
-        $this_model_fields = $this->field_settings(true);
3924
-        if (array_key_exists($query_param, $this_model_fields)) {
3925
-            if ($allow_fields) {
3926
-                return;
3927
-            }
3928
-            throw new EE_Error(
3929
-                sprintf(
3930
-                    __(
3931
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
3932
-                        "event_espresso"
3933
-                    ),
3934
-                    $query_param, get_class($this), $query_param_type, $original_query_param
3935
-                )
3936
-            );
3937
-        }
3938
-        //check if this is a special logic query param
3939
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
3940
-            if ($allow_logic_query_params) {
3941
-                return;
3942
-            }
3943
-            throw new EE_Error(
3944
-                sprintf(
3945
-                    __(
3946
-                        'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
3947
-                        'event_espresso'
3948
-                    ),
3949
-                    implode('", "', $this->_logic_query_param_keys),
3950
-                    $query_param,
3951
-                    get_class($this),
3952
-                    '<br />',
3953
-                    "\t"
3954
-                    . ' $passed_in_query_info = <pre>'
3955
-                    . print_r($passed_in_query_info, true)
3956
-                    . '</pre>'
3957
-                    . "\n\t"
3958
-                    . ' $query_param_type = '
3959
-                    . $query_param_type
3960
-                    . "\n\t"
3961
-                    . ' $original_query_param = '
3962
-                    . $original_query_param
3963
-                )
3964
-            );
3965
-        }
3966
-        //check if it's a custom selection
3967
-        if ($this->_custom_selections instanceof CustomSelects
3968
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
3969
-        ) {
3970
-            return;
3971
-        }
3972
-        //check if has a model name at the beginning
3973
-        //and
3974
-        //check if it's a field on a related model
3975
-        if ($this->extractJoinModelFromQueryParams(
3976
-            $passed_in_query_info,
3977
-            $query_param,
3978
-            $original_query_param,
3979
-            $query_param_type
3980
-        )) {
3981
-            return;
3982
-        }
3983
-
3984
-        //ok so $query_param didn't start with a model name
3985
-        //and we previously confirmed it wasn't a logic query param or field on the current model
3986
-        //it's wack, that's what it is
3987
-        throw new EE_Error(
3988
-            sprintf(
3989
-                esc_html__(
3990
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
3991
-                    "event_espresso"
3992
-                ),
3993
-                $query_param,
3994
-                get_class($this),
3995
-                $query_param_type,
3996
-                $original_query_param
3997
-            )
3998
-        );
3999
-    }
4000
-
4001
-
4002
-    /**
4003
-     * Extracts any possible join model information from the provided possible_join_string.
4004
-     * This method will read the provided $possible_join_string value and determine if there are any possible model join
4005
-     * parts that should be added to the query.
4006
-     *
4007
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4008
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4009
-     * @param null|string                 $original_query_param
4010
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4011
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects' etc.)
4012
-     * @return bool  returns true if a join was added and false if not.
4013
-     * @throws EE_Error
4014
-     */
4015
-    private function extractJoinModelFromQueryParams(
4016
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4017
-        $possible_join_string,
4018
-        $original_query_param,
4019
-        $query_parameter_type
4020
-    ) {
4021
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4022
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4023
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4024
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4025
-                if ($possible_join_string === '') {
4026
-                    //nothing left to $query_param
4027
-                    //we should actually end in a field name, not a model like this!
4028
-                    throw new EE_Error(
4029
-                        sprintf(
4030
-                            esc_html__(
4031
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4032
-                                "event_espresso"
4033
-                            ),
4034
-                            $possible_join_string,
4035
-                            $query_parameter_type,
4036
-                            get_class($this),
4037
-                            $valid_related_model_name
4038
-                        )
4039
-                    );
4040
-                }
4041
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4042
-                $related_model_obj->_extract_related_model_info_from_query_param(
4043
-                    $possible_join_string,
4044
-                    $query_info_carrier,
4045
-                    $query_parameter_type,
4046
-                    $original_query_param
4047
-                );
4048
-                return true;
4049
-            }
4050
-            if ($possible_join_string === $valid_related_model_name) {
4051
-                $this->_add_join_to_model(
4052
-                    $valid_related_model_name,
4053
-                    $query_info_carrier,
4054
-                    $original_query_param
4055
-                );
4056
-                return true;
4057
-            }
4058
-        }
4059
-        return false;
4060
-    }
4061
-
4062
-
4063
-    /**
4064
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4065
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4066
-     * @throws EE_Error
4067
-     */
4068
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4069
-    {
4070
-        if ($this->_custom_selections instanceof CustomSelects
4071
-            && ($this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4072
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4073
-            )
4074
-        ) {
4075
-            $original_selects = $this->_custom_selections->originalSelects();
4076
-            foreach ($original_selects as $alias => $select_configuration) {
4077
-                $this->extractJoinModelFromQueryParams(
4078
-                    $query_info_carrier,
4079
-                    $select_configuration[0],
4080
-                    $select_configuration[0],
4081
-                    'custom_selects'
4082
-                );
4083
-            }
4084
-        }
4085
-    }
4086
-
4087
-
4088
-
4089
-    /**
4090
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4091
-     * and store it on $passed_in_query_info
4092
-     *
4093
-     * @param string                      $model_name
4094
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4095
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4096
-     *                                                          model and $model_name. Eg, if we are querying Event,
4097
-     *                                                          and are adding a join to 'Payment' with the original
4098
-     *                                                          query param key
4099
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4100
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4101
-     *                                                          Payment wants to add default query params so that it
4102
-     *                                                          will know what models to prepend onto its default query
4103
-     *                                                          params or in case it wants to rename tables (in case
4104
-     *                                                          there are multiple joins to the same table)
4105
-     * @return void
4106
-     * @throws EE_Error
4107
-     */
4108
-    private function _add_join_to_model(
4109
-        $model_name,
4110
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4111
-        $original_query_param
4112
-    ) {
4113
-        $relation_obj = $this->related_settings_for($model_name);
4114
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4115
-        //check if the relation is HABTM, because then we're essentially doing two joins
4116
-        //If so, join first to the JOIN table, and add its data types, and then continue as normal
4117
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4118
-            $join_model_obj = $relation_obj->get_join_model();
4119
-            //replace the model specified with the join model for this relation chain, whi
4120
-            $relation_chain_to_join_model = EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain($model_name,
4121
-                $join_model_obj->get_this_model_name(), $model_relation_chain);
4122
-            $passed_in_query_info->merge(
4123
-                new EE_Model_Query_Info_Carrier(
4124
-                    array($relation_chain_to_join_model => $join_model_obj->get_this_model_name()),
4125
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4126
-                )
4127
-            );
4128
-        }
4129
-        //now just join to the other table pointed to by the relation object, and add its data types
4130
-        $passed_in_query_info->merge(
4131
-            new EE_Model_Query_Info_Carrier(
4132
-                array($model_relation_chain => $model_name),
4133
-                $relation_obj->get_join_statement($model_relation_chain)
4134
-            )
4135
-        );
4136
-    }
4137
-
4138
-
4139
-
4140
-    /**
4141
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4142
-     *
4143
-     * @param array $where_params like EEM_Base::get_all
4144
-     * @return string of SQL
4145
-     * @throws EE_Error
4146
-     */
4147
-    private function _construct_where_clause($where_params)
4148
-    {
4149
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4150
-        if ($SQL) {
4151
-            return " WHERE " . $SQL;
4152
-        }
4153
-        return '';
4154
-    }
4155
-
4156
-
4157
-
4158
-    /**
4159
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4160
-     * and should be passed HAVING parameters, not WHERE parameters
4161
-     *
4162
-     * @param array $having_params
4163
-     * @return string
4164
-     * @throws EE_Error
4165
-     */
4166
-    private function _construct_having_clause($having_params)
4167
-    {
4168
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4169
-        if ($SQL) {
4170
-            return " HAVING " . $SQL;
4171
-        }
4172
-        return '';
4173
-    }
4174
-
4175
-
4176
-    /**
4177
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4178
-     * Event_Meta.meta_value = 'foo'))"
4179
-     *
4180
-     * @param array  $where_params see EEM_Base::get_all for documentation
4181
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4182
-     * @throws EE_Error
4183
-     * @return string of SQL
4184
-     */
4185
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4186
-    {
4187
-        $where_clauses = array();
4188
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4189
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);//str_replace("*",'',$query_param);
4190
-            if (in_array($query_param, $this->_logic_query_param_keys)) {
4191
-                switch ($query_param) {
4192
-                    case 'not':
4193
-                    case 'NOT':
4194
-                        $where_clauses[] = "! ("
4195
-                                           . $this->_construct_condition_clause_recursive($op_and_value_or_sub_condition,
4196
-                                $glue)
4197
-                                           . ")";
4198
-                        break;
4199
-                    case 'and':
4200
-                    case 'AND':
4201
-                        $where_clauses[] = " ("
4202
-                                           . $this->_construct_condition_clause_recursive($op_and_value_or_sub_condition,
4203
-                                ' AND ')
4204
-                                           . ")";
4205
-                        break;
4206
-                    case 'or':
4207
-                    case 'OR':
4208
-                        $where_clauses[] = " ("
4209
-                                           . $this->_construct_condition_clause_recursive($op_and_value_or_sub_condition,
4210
-                                ' OR ')
4211
-                                           . ")";
4212
-                        break;
4213
-                }
4214
-            } else {
4215
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4216
-                //if it's not a normal field, maybe it's a custom selection?
4217
-                if (! $field_obj) {
4218
-                    if ($this->_custom_selections instanceof CustomSelects) {
4219
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4220
-                    } else {
4221
-                        throw new EE_Error(sprintf(__("%s is neither a valid model field name, nor a custom selection",
4222
-                            "event_espresso"), $query_param));
4223
-                    }
4224
-                }
4225
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4226
-                $where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4227
-            }
4228
-        }
4229
-        return $where_clauses ? implode($glue, $where_clauses) : '';
4230
-    }
4231
-
4232
-
4233
-
4234
-    /**
4235
-     * Takes the input parameter and extract the table name (alias) and column name
4236
-     *
4237
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4238
-     * @throws EE_Error
4239
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4240
-     */
4241
-    private function _deduce_column_name_from_query_param($query_param)
4242
-    {
4243
-        $field = $this->_deduce_field_from_query_param($query_param);
4244
-        if ($field) {
4245
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param($field->get_model_name(),
4246
-                $query_param);
4247
-            return $table_alias_prefix . $field->get_qualified_column();
4248
-        }
4249
-        if ($this->_custom_selections instanceof CustomSelects
4250
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4251
-        ) {
4252
-            //maybe it's custom selection item?
4253
-            //if so, just use it as the "column name"
4254
-            return $query_param;
4255
-        }
4256
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4257
-            ? implode(',', $this->_custom_selections->columnAliases())
4258
-            : '';
4259
-        throw new EE_Error(
4260
-            sprintf(
4261
-                __(
4262
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4263
-                    "event_espresso"
4264
-                ), $query_param, $custom_select_aliases
4265
-            )
4266
-        );
4267
-    }
4268
-
4269
-
4270
-
4271
-    /**
4272
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4273
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4274
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4275
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4276
-     *
4277
-     * @param string $condition_query_param_key
4278
-     * @return string
4279
-     */
4280
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4281
-    {
4282
-        $pos_of_star = strpos($condition_query_param_key, '*');
4283
-        if ($pos_of_star === false) {
4284
-            return $condition_query_param_key;
4285
-        }
4286
-        $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4287
-        return $condition_query_param_sans_star;
4288
-    }
4289
-
4290
-
4291
-
4292
-    /**
4293
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4294
-     *
4295
-     * @param                            mixed      array | string    $op_and_value
4296
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4297
-     * @throws EE_Error
4298
-     * @return string
4299
-     */
4300
-    private function _construct_op_and_value($op_and_value, $field_obj)
4301
-    {
4302
-        if (is_array($op_and_value)) {
4303
-            $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4304
-            if (! $operator) {
4305
-                $php_array_like_string = array();
4306
-                foreach ($op_and_value as $key => $value) {
4307
-                    $php_array_like_string[] = "$key=>$value";
4308
-                }
4309
-                throw new EE_Error(
4310
-                    sprintf(
4311
-                        __(
4312
-                            "You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4313
-                            "event_espresso"
4314
-                        ),
4315
-                        implode(",", $php_array_like_string)
4316
-                    )
4317
-                );
4318
-            }
4319
-            $value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4320
-        } else {
4321
-            $operator = '=';
4322
-            $value = $op_and_value;
4323
-        }
4324
-        //check to see if the value is actually another field
4325
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4326
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4327
-        }
4328
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4329
-            //in this case, the value should be an array, or at least a comma-separated list
4330
-            //it will need to handle a little differently
4331
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4332
-            //note: $cleaned_value has already been run through $wpdb->prepare()
4333
-            return $operator . SP . $cleaned_value;
4334
-        }
4335
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4336
-            //the value should be an array with count of two.
4337
-            if (count($value) !== 2) {
4338
-                throw new EE_Error(
4339
-                    sprintf(
4340
-                        __(
4341
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4342
-                            'event_espresso'
4343
-                        ),
4344
-                        "BETWEEN"
4345
-                    )
4346
-                );
4347
-            }
4348
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4349
-            return $operator . SP . $cleaned_value;
4350
-        }
4351
-        if (in_array($operator, $this->valid_null_style_operators())) {
4352
-            if ($value !== null) {
4353
-                throw new EE_Error(
4354
-                    sprintf(
4355
-                        __(
4356
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4357
-                            "event_espresso"
4358
-                        ),
4359
-                        $value,
4360
-                        $operator
4361
-                    )
4362
-                );
4363
-            }
4364
-            return $operator;
4365
-        }
4366
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4367
-            //if the operator is 'LIKE', we want to allow percent signs (%) and not
4368
-            //remove other junk. So just treat it as a string.
4369
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4370
-        }
4371
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4372
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4373
-        }
4374
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4375
-            throw new EE_Error(
4376
-                sprintf(
4377
-                    __(
4378
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4379
-                        'event_espresso'
4380
-                    ),
4381
-                    $operator,
4382
-                    $operator
4383
-                )
4384
-            );
4385
-        }
4386
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4387
-            throw new EE_Error(
4388
-                sprintf(
4389
-                    __(
4390
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4391
-                        'event_espresso'
4392
-                    ),
4393
-                    $operator,
4394
-                    $operator
4395
-                )
4396
-            );
4397
-        }
4398
-        throw new EE_Error(
4399
-            sprintf(
4400
-                __(
4401
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4402
-                    "event_espresso"
4403
-                ),
4404
-                http_build_query($op_and_value)
4405
-            )
4406
-        );
4407
-    }
4408
-
4409
-
4410
-
4411
-    /**
4412
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4413
-     *
4414
-     * @param array                      $values
4415
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4416
-     *                                              '%s'
4417
-     * @return string
4418
-     * @throws EE_Error
4419
-     */
4420
-    public function _construct_between_value($values, $field_obj)
4421
-    {
4422
-        $cleaned_values = array();
4423
-        foreach ($values as $value) {
4424
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4425
-        }
4426
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4427
-    }
4428
-
4429
-
4430
-
4431
-    /**
4432
-     * Takes an array or a comma-separated list of $values and cleans them
4433
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4434
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4435
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4436
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4437
-     *
4438
-     * @param mixed                      $values    array or comma-separated string
4439
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4440
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4441
-     * @throws EE_Error
4442
-     */
4443
-    public function _construct_in_value($values, $field_obj)
4444
-    {
4445
-        //check if the value is a CSV list
4446
-        if (is_string($values)) {
4447
-            //in which case, turn it into an array
4448
-            $values = explode(",", $values);
4449
-        }
4450
-        $cleaned_values = array();
4451
-        foreach ($values as $value) {
4452
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4453
-        }
4454
-        //we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4455
-        //but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4456
-        //which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4457
-        if (empty($cleaned_values)) {
4458
-            $all_fields = $this->field_settings();
4459
-            $a_field = array_shift($all_fields);
4460
-            $main_table = $this->_get_main_table();
4461
-            $cleaned_values[] = "SELECT "
4462
-                                . $a_field->get_table_column()
4463
-                                . " FROM "
4464
-                                . $main_table->get_table_name()
4465
-                                . " WHERE FALSE";
4466
-        }
4467
-        return "(" . implode(",", $cleaned_values) . ")";
4468
-    }
4469
-
4470
-
4471
-
4472
-    /**
4473
-     * @param mixed                      $value
4474
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4475
-     * @throws EE_Error
4476
-     * @return false|null|string
4477
-     */
4478
-    private function _wpdb_prepare_using_field($value, $field_obj)
4479
-    {
4480
-        /** @type WPDB $wpdb */
4481
-        global $wpdb;
4482
-        if ($field_obj instanceof EE_Model_Field_Base) {
4483
-            return $wpdb->prepare($field_obj->get_wpdb_data_type(),
4484
-                $this->_prepare_value_for_use_in_db($value, $field_obj));
4485
-        } //$field_obj should really just be a data type
4486
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4487
-            throw new EE_Error(
4488
-                sprintf(
4489
-                    __("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4490
-                    $field_obj, implode(",", $this->_valid_wpdb_data_types)
4491
-                )
4492
-            );
4493
-        }
4494
-        return $wpdb->prepare($field_obj, $value);
4495
-    }
4496
-
4497
-
4498
-
4499
-    /**
4500
-     * Takes the input parameter and finds the model field that it indicates.
4501
-     *
4502
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4503
-     * @throws EE_Error
4504
-     * @return EE_Model_Field_Base
4505
-     */
4506
-    protected function _deduce_field_from_query_param($query_param_name)
4507
-    {
4508
-        //ok, now proceed with deducing which part is the model's name, and which is the field's name
4509
-        //which will help us find the database table and column
4510
-        $query_param_parts = explode(".", $query_param_name);
4511
-        if (empty($query_param_parts)) {
4512
-            throw new EE_Error(sprintf(__("_extract_column_name is empty when trying to extract column and table name from %s",
4513
-                'event_espresso'), $query_param_name));
4514
-        }
4515
-        $number_of_parts = count($query_param_parts);
4516
-        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4517
-        if ($number_of_parts === 1) {
4518
-            $field_name = $last_query_param_part;
4519
-            $model_obj = $this;
4520
-        } else {// $number_of_parts >= 2
4521
-            //the last part is the column name, and there are only 2parts. therefore...
4522
-            $field_name = $last_query_param_part;
4523
-            $model_obj = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4524
-        }
4525
-        try {
4526
-            return $model_obj->field_settings_for($field_name);
4527
-        } catch (EE_Error $e) {
4528
-            return null;
4529
-        }
4530
-    }
4531
-
4532
-
4533
-
4534
-    /**
4535
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4536
-     * alias and column which corresponds to it
4537
-     *
4538
-     * @param string $field_name
4539
-     * @throws EE_Error
4540
-     * @return string
4541
-     */
4542
-    public function _get_qualified_column_for_field($field_name)
4543
-    {
4544
-        $all_fields = $this->field_settings();
4545
-        $field = isset($all_fields[$field_name]) ? $all_fields[$field_name] : false;
4546
-        if ($field) {
4547
-            return $field->get_qualified_column();
4548
-        }
4549
-        throw new EE_Error(
4550
-            sprintf(
4551
-                __(
4552
-                    "There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4553
-                    'event_espresso'
4554
-                ), $field_name, get_class($this)
4555
-            )
4556
-        );
4557
-    }
4558
-
4559
-
4560
-
4561
-    /**
4562
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4563
-     * Example usage:
4564
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4565
-     *      array(),
4566
-     *      ARRAY_A,
4567
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4568
-     *  );
4569
-     * is equivalent to
4570
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4571
-     * and
4572
-     *  EEM_Event::instance()->get_all_wpdb_results(
4573
-     *      array(
4574
-     *          array(
4575
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4576
-     *          ),
4577
-     *          ARRAY_A,
4578
-     *          implode(
4579
-     *              ', ',
4580
-     *              array_merge(
4581
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4582
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4583
-     *              )
4584
-     *          )
4585
-     *      )
4586
-     *  );
4587
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4588
-     *
4589
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4590
-     *                                            and the one whose fields you are selecting for example: when querying
4591
-     *                                            tickets model and selecting fields from the tickets model you would
4592
-     *                                            leave this parameter empty, because no models are needed to join
4593
-     *                                            between the queried model and the selected one. Likewise when
4594
-     *                                            querying the datetime model and selecting fields from the tickets
4595
-     *                                            model, it would also be left empty, because there is a direct
4596
-     *                                            relation from datetimes to tickets, so no model is needed to join
4597
-     *                                            them together. However, when querying from the event model and
4598
-     *                                            selecting fields from the ticket model, you should provide the string
4599
-     *                                            'Datetime', indicating that the event model must first join to the
4600
-     *                                            datetime model in order to find its relation to ticket model.
4601
-     *                                            Also, when querying from the venue model and selecting fields from
4602
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4603
-     *                                            indicating you need to join the venue model to the event model,
4604
-     *                                            to the datetime model, in order to find its relation to the ticket model.
4605
-     *                                            This string is used to deduce the prefix that gets added onto the
4606
-     *                                            models' tables qualified columns
4607
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4608
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4609
-     *                                            qualified column names
4610
-     * @return array|string
4611
-     */
4612
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4613
-    {
4614
-        $table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4615
-        $qualified_columns = array();
4616
-        foreach ($this->field_settings() as $field_name => $field) {
4617
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4618
-        }
4619
-        return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4620
-    }
4621
-
4622
-
4623
-
4624
-    /**
4625
-     * constructs the select use on special limit joins
4626
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4627
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4628
-     * (as that is typically where the limits would be set).
4629
-     *
4630
-     * @param  string       $table_alias The table the select is being built for
4631
-     * @param  mixed|string $limit       The limit for this select
4632
-     * @return string                The final select join element for the query.
4633
-     */
4634
-    public function _construct_limit_join_select($table_alias, $limit)
4635
-    {
4636
-        $SQL = '';
4637
-        foreach ($this->_tables as $table_obj) {
4638
-            if ($table_obj instanceof EE_Primary_Table) {
4639
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4640
-                    ? $table_obj->get_select_join_limit($limit)
4641
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4642
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4643
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4644
-                    ? $table_obj->get_select_join_limit_join($limit)
4645
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4646
-            }
4647
-        }
4648
-        return $SQL;
4649
-    }
4650
-
4651
-
4652
-
4653
-    /**
4654
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4655
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4656
-     *
4657
-     * @return string SQL
4658
-     * @throws EE_Error
4659
-     */
4660
-    public function _construct_internal_join()
4661
-    {
4662
-        $SQL = $this->_get_main_table()->get_table_sql();
4663
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4664
-        return $SQL;
4665
-    }
4666
-
4667
-
4668
-
4669
-    /**
4670
-     * Constructs the SQL for joining all the tables on this model.
4671
-     * Normally $alias should be the primary table's alias, but in cases where
4672
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4673
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4674
-     * alias, this will construct SQL like:
4675
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4676
-     * With $alias being a secondary table's alias, this will construct SQL like:
4677
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4678
-     *
4679
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4680
-     * @return string
4681
-     */
4682
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4683
-    {
4684
-        $SQL = '';
4685
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4686
-        foreach ($this->_tables as $table_obj) {
4687
-            if ($table_obj instanceof EE_Secondary_Table) {//table is secondary table
4688
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4689
-                    //so we're joining to this table, meaning the table is already in
4690
-                    //the FROM statement, BUT the primary table isn't. So we want
4691
-                    //to add the inverse join sql
4692
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4693
-                } else {
4694
-                    //just add a regular JOIN to this table from the primary table
4695
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4696
-                }
4697
-            }//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4698
-        }
4699
-        return $SQL;
4700
-    }
4701
-
4702
-
4703
-
4704
-    /**
4705
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4706
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4707
-     * their data type (eg, '%s', '%d', etc)
4708
-     *
4709
-     * @return array
4710
-     */
4711
-    public function _get_data_types()
4712
-    {
4713
-        $data_types = array();
4714
-        foreach ($this->field_settings() as $field_obj) {
4715
-            //$data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4716
-            /** @var $field_obj EE_Model_Field_Base */
4717
-            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4718
-        }
4719
-        return $data_types;
4720
-    }
4721
-
4722
-
4723
-
4724
-    /**
4725
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4726
-     *
4727
-     * @param string $model_name
4728
-     * @throws EE_Error
4729
-     * @return EEM_Base
4730
-     */
4731
-    public function get_related_model_obj($model_name)
4732
-    {
4733
-        $model_classname = "EEM_" . $model_name;
4734
-        if (! class_exists($model_classname)) {
4735
-            throw new EE_Error(sprintf(__("You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4736
-                'event_espresso'), $model_name, $model_classname));
4737
-        }
4738
-        return call_user_func($model_classname . "::instance");
4739
-    }
4740
-
4741
-
4742
-
4743
-    /**
4744
-     * Returns the array of EE_ModelRelations for this model.
4745
-     *
4746
-     * @return EE_Model_Relation_Base[]
4747
-     */
4748
-    public function relation_settings()
4749
-    {
4750
-        return $this->_model_relations;
4751
-    }
4752
-
4753
-
4754
-
4755
-    /**
4756
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4757
-     * because without THOSE models, this model probably doesn't have much purpose.
4758
-     * (Eg, without an event, datetimes have little purpose.)
4759
-     *
4760
-     * @return EE_Belongs_To_Relation[]
4761
-     */
4762
-    public function belongs_to_relations()
4763
-    {
4764
-        $belongs_to_relations = array();
4765
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4766
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
4767
-                $belongs_to_relations[$model_name] = $relation_obj;
4768
-            }
4769
-        }
4770
-        return $belongs_to_relations;
4771
-    }
4772
-
4773
-
4774
-
4775
-    /**
4776
-     * Returns the specified EE_Model_Relation, or throws an exception
4777
-     *
4778
-     * @param string $relation_name name of relation, key in $this->_relatedModels
4779
-     * @throws EE_Error
4780
-     * @return EE_Model_Relation_Base
4781
-     */
4782
-    public function related_settings_for($relation_name)
4783
-    {
4784
-        $relatedModels = $this->relation_settings();
4785
-        if (! array_key_exists($relation_name, $relatedModels)) {
4786
-            throw new EE_Error(
4787
-                sprintf(
4788
-                    __('Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4789
-                        'event_espresso'),
4790
-                    $relation_name,
4791
-                    $this->_get_class_name(),
4792
-                    implode(', ', array_keys($relatedModels))
4793
-                )
4794
-            );
4795
-        }
4796
-        return $relatedModels[$relation_name];
4797
-    }
4798
-
4799
-
4800
-
4801
-    /**
4802
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4803
-     * fields
4804
-     *
4805
-     * @param string $fieldName
4806
-     * @param boolean $include_db_only_fields
4807
-     * @throws EE_Error
4808
-     * @return EE_Model_Field_Base
4809
-     */
4810
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
4811
-    {
4812
-        $fieldSettings = $this->field_settings($include_db_only_fields);
4813
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4814
-            throw new EE_Error(sprintf(__("There is no field/column '%s' on '%s'", 'event_espresso'), $fieldName,
4815
-                get_class($this)));
4816
-        }
4817
-        return $fieldSettings[$fieldName];
4818
-    }
4819
-
4820
-
4821
-
4822
-    /**
4823
-     * Checks if this field exists on this model
4824
-     *
4825
-     * @param string $fieldName a key in the model's _field_settings array
4826
-     * @return boolean
4827
-     */
4828
-    public function has_field($fieldName)
4829
-    {
4830
-        $fieldSettings = $this->field_settings(true);
4831
-        if (isset($fieldSettings[$fieldName])) {
4832
-            return true;
4833
-        }
4834
-        return false;
4835
-    }
4836
-
4837
-
4838
-
4839
-    /**
4840
-     * Returns whether or not this model has a relation to the specified model
4841
-     *
4842
-     * @param string $relation_name possibly one of the keys in the relation_settings array
4843
-     * @return boolean
4844
-     */
4845
-    public function has_relation($relation_name)
4846
-    {
4847
-        $relations = $this->relation_settings();
4848
-        if (isset($relations[$relation_name])) {
4849
-            return true;
4850
-        }
4851
-        return false;
4852
-    }
4853
-
4854
-
4855
-
4856
-    /**
4857
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4858
-     * Eg, on EE_Answer that would be ANS_ID field object
4859
-     *
4860
-     * @param $field_obj
4861
-     * @return boolean
4862
-     */
4863
-    public function is_primary_key_field($field_obj)
4864
-    {
4865
-        return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
4866
-    }
4867
-
4868
-
4869
-
4870
-    /**
4871
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4872
-     * Eg, on EE_Answer that would be ANS_ID field object
4873
-     *
4874
-     * @return EE_Model_Field_Base
4875
-     * @throws EE_Error
4876
-     */
4877
-    public function get_primary_key_field()
4878
-    {
4879
-        if ($this->_primary_key_field === null) {
4880
-            foreach ($this->field_settings(true) as $field_obj) {
4881
-                if ($this->is_primary_key_field($field_obj)) {
4882
-                    $this->_primary_key_field = $field_obj;
4883
-                    break;
4884
-                }
4885
-            }
4886
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
4887
-                throw new EE_Error(sprintf(__("There is no Primary Key defined on model %s", 'event_espresso'),
4888
-                    get_class($this)));
4889
-            }
4890
-        }
4891
-        return $this->_primary_key_field;
4892
-    }
4893
-
4894
-
4895
-
4896
-    /**
4897
-     * Returns whether or not not there is a primary key on this model.
4898
-     * Internally does some caching.
4899
-     *
4900
-     * @return boolean
4901
-     */
4902
-    public function has_primary_key_field()
4903
-    {
4904
-        if ($this->_has_primary_key_field === null) {
4905
-            try {
4906
-                $this->get_primary_key_field();
4907
-                $this->_has_primary_key_field = true;
4908
-            } catch (EE_Error $e) {
4909
-                $this->_has_primary_key_field = false;
4910
-            }
4911
-        }
4912
-        return $this->_has_primary_key_field;
4913
-    }
4914
-
4915
-
4916
-
4917
-    /**
4918
-     * Finds the first field of type $field_class_name.
4919
-     *
4920
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
4921
-     *                                 EE_Foreign_Key_Field, etc
4922
-     * @return EE_Model_Field_Base or null if none is found
4923
-     */
4924
-    public function get_a_field_of_type($field_class_name)
4925
-    {
4926
-        foreach ($this->field_settings() as $field) {
4927
-            if ($field instanceof $field_class_name) {
4928
-                return $field;
4929
-            }
4930
-        }
4931
-        return null;
4932
-    }
4933
-
4934
-
4935
-
4936
-    /**
4937
-     * Gets a foreign key field pointing to model.
4938
-     *
4939
-     * @param string $model_name eg Event, Registration, not EEM_Event
4940
-     * @return EE_Foreign_Key_Field_Base
4941
-     * @throws EE_Error
4942
-     */
4943
-    public function get_foreign_key_to($model_name)
4944
-    {
4945
-        if (! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4946
-            foreach ($this->field_settings() as $field) {
4947
-                if (
4948
-                    $field instanceof EE_Foreign_Key_Field_Base
4949
-                    && in_array($model_name, $field->get_model_names_pointed_to())
4950
-                ) {
4951
-                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
4952
-                    break;
4953
-                }
4954
-            }
4955
-            if (! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4956
-                throw new EE_Error(sprintf(__("There is no foreign key field pointing to model %s on model %s",
4957
-                    'event_espresso'), $model_name, get_class($this)));
4958
-            }
4959
-        }
4960
-        return $this->_cache_foreign_key_to_fields[$model_name];
4961
-    }
4962
-
4963
-
4964
-
4965
-    /**
4966
-     * Gets the table name (including $wpdb->prefix) for the table alias
4967
-     *
4968
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
4969
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
4970
-     *                            Either one works
4971
-     * @return string
4972
-     */
4973
-    public function get_table_for_alias($table_alias)
4974
-    {
4975
-        $table_alias_sans_model_relation_chain_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
4976
-        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
4977
-    }
4978
-
4979
-
4980
-
4981
-    /**
4982
-     * Returns a flat array of all field son this model, instead of organizing them
4983
-     * by table_alias as they are in the constructor.
4984
-     *
4985
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
4986
-     * @return EE_Model_Field_Base[] where the keys are the field's name
4987
-     */
4988
-    public function field_settings($include_db_only_fields = false)
4989
-    {
4990
-        if ($include_db_only_fields) {
4991
-            if ($this->_cached_fields === null) {
4992
-                $this->_cached_fields = array();
4993
-                foreach ($this->_fields as $fields_corresponding_to_table) {
4994
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
4995
-                        $this->_cached_fields[$field_name] = $field_obj;
4996
-                    }
4997
-                }
4998
-            }
4999
-            return $this->_cached_fields;
5000
-        }
5001
-        if ($this->_cached_fields_non_db_only === null) {
5002
-            $this->_cached_fields_non_db_only = array();
5003
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5004
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5005
-                    /** @var $field_obj EE_Model_Field_Base */
5006
-                    if (! $field_obj->is_db_only_field()) {
5007
-                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5008
-                    }
5009
-                }
5010
-            }
5011
-        }
5012
-        return $this->_cached_fields_non_db_only;
5013
-    }
5014
-
5015
-
5016
-
5017
-    /**
5018
-     *        cycle though array of attendees and create objects out of each item
5019
-     *
5020
-     * @access        private
5021
-     * @param        array $rows of results of $wpdb->get_results($query,ARRAY_A)
5022
-     * @return \EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5023
-     *                           numerically indexed)
5024
-     * @throws EE_Error
5025
-     */
5026
-    protected function _create_objects($rows = array())
5027
-    {
5028
-        $array_of_objects = array();
5029
-        if (empty($rows)) {
5030
-            return array();
5031
-        }
5032
-        $count_if_model_has_no_primary_key = 0;
5033
-        $has_primary_key = $this->has_primary_key_field();
5034
-        $primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5035
-        foreach ((array)$rows as $row) {
5036
-            if (empty($row)) {
5037
-                //wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5038
-                return array();
5039
-            }
5040
-            //check if we've already set this object in the results array,
5041
-            //in which case there's no need to process it further (again)
5042
-            if ($has_primary_key) {
5043
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5044
-                    $row,
5045
-                    $primary_key_field->get_qualified_column(),
5046
-                    $primary_key_field->get_table_column()
5047
-                );
5048
-                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5049
-                    continue;
5050
-                }
5051
-            }
5052
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5053
-            if (! $classInstance) {
5054
-                throw new EE_Error(
5055
-                    sprintf(
5056
-                        __('Could not create instance of class %s from row %s', 'event_espresso'),
5057
-                        $this->get_this_model_name(),
5058
-                        http_build_query($row)
5059
-                    )
5060
-                );
5061
-            }
5062
-            //set the timezone on the instantiated objects
5063
-            $classInstance->set_timezone($this->_timezone);
5064
-            //make sure if there is any timezone setting present that we set the timezone for the object
5065
-            $key = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5066
-            $array_of_objects[$key] = $classInstance;
5067
-            //also, for all the relations of type BelongsTo, see if we can cache
5068
-            //those related models
5069
-            //(we could do this for other relations too, but if there are conditions
5070
-            //that filtered out some fo the results, then we'd be caching an incomplete set
5071
-            //so it requires a little more thought than just caching them immediately...)
5072
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5073
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5074
-                    //check if this model's INFO is present. If so, cache it on the model
5075
-                    $other_model = $relation_obj->get_other_model();
5076
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5077
-                    //if we managed to make a model object from the results, cache it on the main model object
5078
-                    if ($other_model_obj_maybe) {
5079
-                        //set timezone on these other model objects if they are present
5080
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5081
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5082
-                    }
5083
-                }
5084
-            }
5085
-            //also, if this was a custom select query, let's see if there are any results for the custom select fields
5086
-            //and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5087
-            //the field in the CustomSelects object
5088
-            if ($this->_custom_selections instanceof CustomSelects) {
5089
-                $classInstance->setCustomSelectsValues(
5090
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5091
-                );
5092
-            }
5093
-        }
5094
-        return $array_of_objects;
5095
-    }
5096
-
5097
-
5098
-    /**
5099
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5100
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5101
-     *
5102
-     * @param array $db_results_row
5103
-     * @return array
5104
-     */
5105
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5106
-    {
5107
-        $results = array();
5108
-        if ($this->_custom_selections instanceof CustomSelects) {
5109
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5110
-                if (isset($db_results_row[$alias])) {
5111
-                    $results[$alias] = $this->convertValueToDataType(
5112
-                        $db_results_row[$alias],
5113
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5114
-                    );
5115
-                }
5116
-            }
5117
-        }
5118
-        return $results;
5119
-    }
5120
-
5121
-
5122
-    /**
5123
-     * This will set the value for the given alias
5124
-     * @param string $value
5125
-     * @param string $datatype (one of %d, %s, %f)
5126
-     * @return int|string|float (int for %d, string for %s, float for %f)
5127
-     */
5128
-    protected function convertValueToDataType($value, $datatype)
5129
-    {
5130
-        switch ($datatype) {
5131
-            case '%f':
5132
-                return (float) $value;
5133
-            case '%d':
5134
-                return (int) $value;
5135
-            default:
5136
-                return (string) $value;
5137
-        }
5138
-    }
5139
-
5140
-
5141
-    /**
5142
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5143
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5144
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5145
-     * object (as set in the model_field!).
5146
-     *
5147
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5148
-     */
5149
-    public function create_default_object()
5150
-    {
5151
-        $this_model_fields_and_values = array();
5152
-        //setup the row using default values;
5153
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5154
-            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5155
-        }
5156
-        $className = $this->_get_class_name();
5157
-        $classInstance = EE_Registry::instance()
5158
-                                    ->load_class($className, array($this_model_fields_and_values), false, false);
5159
-        return $classInstance;
5160
-    }
5161
-
5162
-
5163
-
5164
-    /**
5165
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5166
-     *                             or an stdClass where each property is the name of a column,
5167
-     * @return EE_Base_Class
5168
-     * @throws EE_Error
5169
-     */
5170
-    public function instantiate_class_from_array_or_object($cols_n_values)
5171
-    {
5172
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5173
-            $cols_n_values = get_object_vars($cols_n_values);
5174
-        }
5175
-        $primary_key = null;
5176
-        //make sure the array only has keys that are fields/columns on this model
5177
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5178
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5179
-            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5180
-        }
5181
-        $className = $this->_get_class_name();
5182
-        //check we actually found results that we can use to build our model object
5183
-        //if not, return null
5184
-        if ($this->has_primary_key_field()) {
5185
-            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5186
-                return null;
5187
-            }
5188
-        } else if ($this->unique_indexes()) {
5189
-            $first_column = reset($this_model_fields_n_values);
5190
-            if (empty($first_column)) {
5191
-                return null;
5192
-            }
5193
-        }
5194
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5195
-        if ($primary_key) {
5196
-            $classInstance = $this->get_from_entity_map($primary_key);
5197
-            if (! $classInstance) {
5198
-                $classInstance = EE_Registry::instance()
5199
-                                            ->load_class($className,
5200
-                                                array($this_model_fields_n_values, $this->_timezone), true, false);
5201
-                // add this new object to the entity map
5202
-                $classInstance = $this->add_to_entity_map($classInstance);
5203
-            }
5204
-        } else {
5205
-            $classInstance = EE_Registry::instance()
5206
-                                        ->load_class($className, array($this_model_fields_n_values, $this->_timezone),
5207
-                                            true, false);
5208
-        }
5209
-        return $classInstance;
5210
-    }
5211
-
5212
-
5213
-
5214
-    /**
5215
-     * Gets the model object from the  entity map if it exists
5216
-     *
5217
-     * @param int|string $id the ID of the model object
5218
-     * @return EE_Base_Class
5219
-     */
5220
-    public function get_from_entity_map($id)
5221
-    {
5222
-        return isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])
5223
-            ? $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] : null;
5224
-    }
5225
-
5226
-
5227
-
5228
-    /**
5229
-     * add_to_entity_map
5230
-     * Adds the object to the model's entity mappings
5231
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5232
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5233
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5234
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5235
-     *        then this method should be called immediately after the update query
5236
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5237
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5238
-     *
5239
-     * @param    EE_Base_Class $object
5240
-     * @throws EE_Error
5241
-     * @return \EE_Base_Class
5242
-     */
5243
-    public function add_to_entity_map(EE_Base_Class $object)
5244
-    {
5245
-        $className = $this->_get_class_name();
5246
-        if (! $object instanceof $className) {
5247
-            throw new EE_Error(sprintf(__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5248
-                is_object($object) ? get_class($object) : $object, $className));
5249
-        }
5250
-        /** @var $object EE_Base_Class */
5251
-        if (! $object->ID()) {
5252
-            throw new EE_Error(sprintf(__("You tried storing a model object with NO ID in the %s entity mapper.",
5253
-                "event_espresso"), get_class($this)));
5254
-        }
5255
-        // double check it's not already there
5256
-        $classInstance = $this->get_from_entity_map($object->ID());
5257
-        if ($classInstance) {
5258
-            return $classInstance;
5259
-        }
5260
-        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5261
-        return $object;
5262
-    }
5263
-
5264
-
5265
-
5266
-    /**
5267
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5268
-     * if no identifier is provided, then the entire entity map is emptied
5269
-     *
5270
-     * @param int|string $id the ID of the model object
5271
-     * @return boolean
5272
-     */
5273
-    public function clear_entity_map($id = null)
5274
-    {
5275
-        if (empty($id)) {
5276
-            $this->_entity_map[EEM_Base::$_model_query_blog_id] = array();
5277
-            return true;
5278
-        }
5279
-        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5280
-            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5281
-            return true;
5282
-        }
5283
-        return false;
5284
-    }
5285
-
5286
-
5287
-
5288
-    /**
5289
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5290
-     * Given an array where keys are column (or column alias) names and values,
5291
-     * returns an array of their corresponding field names and database values
5292
-     *
5293
-     * @param array $cols_n_values
5294
-     * @return array
5295
-     */
5296
-    public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5297
-    {
5298
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5299
-    }
5300
-
5301
-
5302
-
5303
-    /**
5304
-     * _deduce_fields_n_values_from_cols_n_values
5305
-     * Given an array where keys are column (or column alias) names and values,
5306
-     * returns an array of their corresponding field names and database values
5307
-     *
5308
-     * @param string $cols_n_values
5309
-     * @return array
5310
-     */
5311
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5312
-    {
5313
-        $this_model_fields_n_values = array();
5314
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5315
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not($cols_n_values,
5316
-                $table_obj->get_fully_qualified_pk_column(), $table_obj->get_pk_column());
5317
-            //there is a primary key on this table and its not set. Use defaults for all its columns
5318
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5319
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5320
-                    if (! $field_obj->is_db_only_field()) {
5321
-                        //prepare field as if its coming from db
5322
-                        $prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5323
-                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5324
-                    }
5325
-                }
5326
-            } else {
5327
-                //the table's rows existed. Use their values
5328
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5329
-                    if (! $field_obj->is_db_only_field()) {
5330
-                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5331
-                            $cols_n_values, $field_obj->get_qualified_column(),
5332
-                            $field_obj->get_table_column()
5333
-                        );
5334
-                    }
5335
-                }
5336
-            }
5337
-        }
5338
-        return $this_model_fields_n_values;
5339
-    }
5340
-
5341
-
5342
-
5343
-    /**
5344
-     * @param $cols_n_values
5345
-     * @param $qualified_column
5346
-     * @param $regular_column
5347
-     * @return null
5348
-     */
5349
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5350
-    {
5351
-        $value = null;
5352
-        //ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5353
-        //does the field on the model relate to this column retrieved from the db?
5354
-        //or is it a db-only field? (not relating to the model)
5355
-        if (isset($cols_n_values[$qualified_column])) {
5356
-            $value = $cols_n_values[$qualified_column];
5357
-        } elseif (isset($cols_n_values[$regular_column])) {
5358
-            $value = $cols_n_values[$regular_column];
5359
-        }
5360
-        return $value;
5361
-    }
5362
-
5363
-
5364
-
5365
-    /**
5366
-     * refresh_entity_map_from_db
5367
-     * Makes sure the model object in the entity map at $id assumes the values
5368
-     * of the database (opposite of EE_base_Class::save())
5369
-     *
5370
-     * @param int|string $id
5371
-     * @return EE_Base_Class
5372
-     * @throws EE_Error
5373
-     */
5374
-    public function refresh_entity_map_from_db($id)
5375
-    {
5376
-        $obj_in_map = $this->get_from_entity_map($id);
5377
-        if ($obj_in_map) {
5378
-            $wpdb_results = $this->_get_all_wpdb_results(
5379
-                array(array($this->get_primary_key_field()->get_name() => $id), 'limit' => 1)
5380
-            );
5381
-            if ($wpdb_results && is_array($wpdb_results)) {
5382
-                $one_row = reset($wpdb_results);
5383
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5384
-                    $obj_in_map->set_from_db($field_name, $db_value);
5385
-                }
5386
-                //clear the cache of related model objects
5387
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5388
-                    $obj_in_map->clear_cache($relation_name, null, true);
5389
-                }
5390
-            }
5391
-            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5392
-            return $obj_in_map;
5393
-        }
5394
-        return $this->get_one_by_ID($id);
5395
-    }
5396
-
5397
-
5398
-
5399
-    /**
5400
-     * refresh_entity_map_with
5401
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5402
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5403
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5404
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5405
-     *
5406
-     * @param int|string    $id
5407
-     * @param EE_Base_Class $replacing_model_obj
5408
-     * @return \EE_Base_Class
5409
-     * @throws EE_Error
5410
-     */
5411
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5412
-    {
5413
-        $obj_in_map = $this->get_from_entity_map($id);
5414
-        if ($obj_in_map) {
5415
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5416
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5417
-                    $obj_in_map->set($field_name, $value);
5418
-                }
5419
-                //make the model object in the entity map's cache match the $replacing_model_obj
5420
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5421
-                    $obj_in_map->clear_cache($relation_name, null, true);
5422
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5423
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5424
-                    }
5425
-                }
5426
-            }
5427
-            return $obj_in_map;
5428
-        }
5429
-        $this->add_to_entity_map($replacing_model_obj);
5430
-        return $replacing_model_obj;
5431
-    }
5432
-
5433
-
5434
-
5435
-    /**
5436
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5437
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5438
-     * require_once($this->_getClassName().".class.php");
5439
-     *
5440
-     * @return string
5441
-     */
5442
-    private function _get_class_name()
5443
-    {
5444
-        return "EE_" . $this->get_this_model_name();
5445
-    }
5446
-
5447
-
5448
-
5449
-    /**
5450
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5451
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5452
-     * it would be 'Events'.
5453
-     *
5454
-     * @param int $quantity
5455
-     * @return string
5456
-     */
5457
-    public function item_name($quantity = 1)
5458
-    {
5459
-        return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5460
-    }
5461
-
5462
-
5463
-
5464
-    /**
5465
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5466
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5467
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5468
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5469
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5470
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5471
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5472
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5473
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5474
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5475
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5476
-     *        return $previousReturnValue.$returnString;
5477
-     * }
5478
-     * require('EEM_Answer.model.php');
5479
-     * $answer=EEM_Answer::instance();
5480
-     * echo $answer->my_callback('monkeys',100);
5481
-     * //will output "you called my_callback! and passed args:monkeys,100"
5482
-     *
5483
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5484
-     * @param array  $args       array of original arguments passed to the function
5485
-     * @throws EE_Error
5486
-     * @return mixed whatever the plugin which calls add_filter decides
5487
-     */
5488
-    public function __call($methodName, $args)
5489
-    {
5490
-        $className = get_class($this);
5491
-        $tagName = "FHEE__{$className}__{$methodName}";
5492
-        if (! has_filter($tagName)) {
5493
-            throw new EE_Error(
5494
-                sprintf(
5495
-                    __('Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5496
-                        'event_espresso'),
5497
-                    $methodName,
5498
-                    $className,
5499
-                    $tagName,
5500
-                    '<br />'
5501
-                )
5502
-            );
5503
-        }
5504
-        return apply_filters($tagName, null, $this, $args);
5505
-    }
5506
-
5507
-
5508
-
5509
-    /**
5510
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5511
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5512
-     *
5513
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5514
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5515
-     *                                                       the object's class name
5516
-     *                                                       or object's ID
5517
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5518
-     *                                                       exists in the database. If it does not, we add it
5519
-     * @throws EE_Error
5520
-     * @return EE_Base_Class
5521
-     */
5522
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5523
-    {
5524
-        $className = $this->_get_class_name();
5525
-        if ($base_class_obj_or_id instanceof $className) {
5526
-            $model_object = $base_class_obj_or_id;
5527
-        } else {
5528
-            $primary_key_field = $this->get_primary_key_field();
5529
-            if (
5530
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5531
-                && (
5532
-                    is_int($base_class_obj_or_id)
5533
-                    || is_string($base_class_obj_or_id)
5534
-                )
5535
-            ) {
5536
-                // assume it's an ID.
5537
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5538
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5539
-            } else if (
5540
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5541
-                && is_string($base_class_obj_or_id)
5542
-            ) {
5543
-                // assume its a string representation of the object
5544
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5545
-            } else {
5546
-                throw new EE_Error(
5547
-                    sprintf(
5548
-                        __(
5549
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5550
-                            'event_espresso'
5551
-                        ),
5552
-                        $base_class_obj_or_id,
5553
-                        $this->_get_class_name(),
5554
-                        print_r($base_class_obj_or_id, true)
5555
-                    )
5556
-                );
5557
-            }
5558
-        }
5559
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5560
-            $model_object->save();
5561
-        }
5562
-        return $model_object;
5563
-    }
5564
-
5565
-
5566
-
5567
-    /**
5568
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5569
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5570
-     * returns it ID.
5571
-     *
5572
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5573
-     * @return int|string depending on the type of this model object's ID
5574
-     * @throws EE_Error
5575
-     */
5576
-    public function ensure_is_ID($base_class_obj_or_id)
5577
-    {
5578
-        $className = $this->_get_class_name();
5579
-        if ($base_class_obj_or_id instanceof $className) {
5580
-            /** @var $base_class_obj_or_id EE_Base_Class */
5581
-            $id = $base_class_obj_or_id->ID();
5582
-        } elseif (is_int($base_class_obj_or_id)) {
5583
-            //assume it's an ID
5584
-            $id = $base_class_obj_or_id;
5585
-        } elseif (is_string($base_class_obj_or_id)) {
5586
-            //assume its a string representation of the object
5587
-            $id = $base_class_obj_or_id;
5588
-        } else {
5589
-            throw new EE_Error(sprintf(__("'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5590
-                'event_espresso'), $base_class_obj_or_id, $this->_get_class_name(),
5591
-                print_r($base_class_obj_or_id, true)));
5592
-        }
5593
-        return $id;
5594
-    }
5595
-
5596
-
5597
-
5598
-    /**
5599
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5600
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5601
-     * been sanitized and converted into the appropriate domain.
5602
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5603
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5604
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5605
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5606
-     * $EVT = EEM_Event::instance(); $old_setting =
5607
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5608
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5609
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5610
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5611
-     *
5612
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5613
-     * @return void
5614
-     */
5615
-    public function assume_values_already_prepared_by_model_object(
5616
-        $values_already_prepared = self::not_prepared_by_model_object
5617
-    ) {
5618
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5619
-    }
5620
-
5621
-
5622
-
5623
-    /**
5624
-     * Read comments for assume_values_already_prepared_by_model_object()
5625
-     *
5626
-     * @return int
5627
-     */
5628
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5629
-    {
5630
-        return $this->_values_already_prepared_by_model_object;
5631
-    }
5632
-
5633
-
5634
-
5635
-    /**
5636
-     * Gets all the indexes on this model
5637
-     *
5638
-     * @return EE_Index[]
5639
-     */
5640
-    public function indexes()
5641
-    {
5642
-        return $this->_indexes;
5643
-    }
5644
-
5645
-
5646
-
5647
-    /**
5648
-     * Gets all the Unique Indexes on this model
5649
-     *
5650
-     * @return EE_Unique_Index[]
5651
-     */
5652
-    public function unique_indexes()
5653
-    {
5654
-        $unique_indexes = array();
5655
-        foreach ($this->_indexes as $name => $index) {
5656
-            if ($index instanceof EE_Unique_Index) {
5657
-                $unique_indexes [$name] = $index;
5658
-            }
5659
-        }
5660
-        return $unique_indexes;
5661
-    }
5662
-
5663
-
5664
-
5665
-    /**
5666
-     * Gets all the fields which, when combined, make the primary key.
5667
-     * This is usually just an array with 1 element (the primary key), but in cases
5668
-     * where there is no primary key, it's a combination of fields as defined
5669
-     * on a primary index
5670
-     *
5671
-     * @return EE_Model_Field_Base[] indexed by the field's name
5672
-     * @throws EE_Error
5673
-     */
5674
-    public function get_combined_primary_key_fields()
5675
-    {
5676
-        foreach ($this->indexes() as $index) {
5677
-            if ($index instanceof EE_Primary_Key_Index) {
5678
-                return $index->fields();
5679
-            }
5680
-        }
5681
-        return array($this->primary_key_name() => $this->get_primary_key_field());
5682
-    }
5683
-
5684
-
5685
-
5686
-    /**
5687
-     * Used to build a primary key string (when the model has no primary key),
5688
-     * which can be used a unique string to identify this model object.
5689
-     *
5690
-     * @param array $cols_n_values keys are field names, values are their values
5691
-     * @return string
5692
-     * @throws EE_Error
5693
-     */
5694
-    public function get_index_primary_key_string($cols_n_values)
5695
-    {
5696
-        $cols_n_values_for_primary_key_index = array_intersect_key($cols_n_values,
5697
-            $this->get_combined_primary_key_fields());
5698
-        return http_build_query($cols_n_values_for_primary_key_index);
5699
-    }
5700
-
5701
-
5702
-
5703
-    /**
5704
-     * Gets the field values from the primary key string
5705
-     *
5706
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5707
-     * @param string $index_primary_key_string
5708
-     * @return null|array
5709
-     * @throws EE_Error
5710
-     */
5711
-    public function parse_index_primary_key_string($index_primary_key_string)
5712
-    {
5713
-        $key_fields = $this->get_combined_primary_key_fields();
5714
-        //check all of them are in the $id
5715
-        $key_vals_in_combined_pk = array();
5716
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5717
-        foreach ($key_fields as $key_field_name => $field_obj) {
5718
-            if (! isset($key_vals_in_combined_pk[$key_field_name])) {
5719
-                return null;
5720
-            }
5721
-        }
5722
-        return $key_vals_in_combined_pk;
5723
-    }
5724
-
5725
-
5726
-
5727
-    /**
5728
-     * verifies that an array of key-value pairs for model fields has a key
5729
-     * for each field comprising the primary key index
5730
-     *
5731
-     * @param array $key_vals
5732
-     * @return boolean
5733
-     * @throws EE_Error
5734
-     */
5735
-    public function has_all_combined_primary_key_fields($key_vals)
5736
-    {
5737
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5738
-        foreach ($keys_it_should_have as $key) {
5739
-            if (! isset($key_vals[$key])) {
5740
-                return false;
5741
-            }
5742
-        }
5743
-        return true;
5744
-    }
5745
-
5746
-
5747
-
5748
-    /**
5749
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5750
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
5751
-     *
5752
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5753
-     * @param array               $query_params                     like EEM_Base::get_all's query_params.
5754
-     * @throws EE_Error
5755
-     * @return \EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5756
-     *                                                              indexed)
5757
-     */
5758
-    public function get_all_copies($model_object_or_attributes_array, $query_params = array())
5759
-    {
5760
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5761
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
5762
-        } elseif (is_array($model_object_or_attributes_array)) {
5763
-            $attributes_array = $model_object_or_attributes_array;
5764
-        } else {
5765
-            throw new EE_Error(sprintf(__("get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5766
-                "event_espresso"), $model_object_or_attributes_array));
5767
-        }
5768
-        //even copies obviously won't have the same ID, so remove the primary key
5769
-        //from the WHERE conditions for finding copies (if there is a primary key, of course)
5770
-        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
5771
-            unset($attributes_array[$this->primary_key_name()]);
5772
-        }
5773
-        if (isset($query_params[0])) {
5774
-            $query_params[0] = array_merge($attributes_array, $query_params);
5775
-        } else {
5776
-            $query_params[0] = $attributes_array;
5777
-        }
5778
-        return $this->get_all($query_params);
5779
-    }
5780
-
5781
-
5782
-
5783
-    /**
5784
-     * Gets the first copy we find. See get_all_copies for more details
5785
-     *
5786
-     * @param       mixed EE_Base_Class | array        $model_object_or_attributes_array
5787
-     * @param array $query_params
5788
-     * @return EE_Base_Class
5789
-     * @throws EE_Error
5790
-     */
5791
-    public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5792
-    {
5793
-        if (! is_array($query_params)) {
5794
-            EE_Error::doing_it_wrong('EEM_Base::get_one_copy',
5795
-                sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5796
-                    gettype($query_params)), '4.6.0');
5797
-            $query_params = array();
5798
-        }
5799
-        $query_params['limit'] = 1;
5800
-        $copies = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5801
-        if (is_array($copies)) {
5802
-            return array_shift($copies);
5803
-        }
5804
-        return null;
5805
-    }
5806
-
5807
-
5808
-
5809
-    /**
5810
-     * Updates the item with the specified id. Ignores default query parameters because
5811
-     * we have specified the ID, and its assumed we KNOW what we're doing
5812
-     *
5813
-     * @param array      $fields_n_values keys are field names, values are their new values
5814
-     * @param int|string $id              the value of the primary key to update
5815
-     * @return int number of rows updated
5816
-     * @throws EE_Error
5817
-     */
5818
-    public function update_by_ID($fields_n_values, $id)
5819
-    {
5820
-        $query_params = array(
5821
-            0                          => array($this->get_primary_key_field()->get_name() => $id),
5822
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
5823
-        );
5824
-        return $this->update($fields_n_values, $query_params);
5825
-    }
5826
-
5827
-
5828
-
5829
-    /**
5830
-     * Changes an operator which was supplied to the models into one usable in SQL
5831
-     *
5832
-     * @param string $operator_supplied
5833
-     * @return string an operator which can be used in SQL
5834
-     * @throws EE_Error
5835
-     */
5836
-    private function _prepare_operator_for_sql($operator_supplied)
5837
-    {
5838
-        $sql_operator = isset($this->_valid_operators[$operator_supplied]) ? $this->_valid_operators[$operator_supplied]
5839
-            : null;
5840
-        if ($sql_operator) {
5841
-            return $sql_operator;
5842
-        }
5843
-        throw new EE_Error(
5844
-            sprintf(
5845
-                __(
5846
-                    "The operator '%s' is not in the list of valid operators: %s",
5847
-                    "event_espresso"
5848
-                ), $operator_supplied, implode(",", array_keys($this->_valid_operators))
5849
-            )
5850
-        );
5851
-    }
5852
-
5853
-
5854
-
5855
-    /**
5856
-     * Gets the valid operators
5857
-     * @return array keys are accepted strings, values are the SQL they are converted to
5858
-     */
5859
-    public function valid_operators(){
5860
-        return $this->_valid_operators;
5861
-    }
5862
-
5863
-
5864
-
5865
-    /**
5866
-     * Gets the between-style operators (take 2 arguments).
5867
-     * @return array keys are accepted strings, values are the SQL they are converted to
5868
-     */
5869
-    public function valid_between_style_operators()
5870
-    {
5871
-        return array_intersect(
5872
-            $this->valid_operators(),
5873
-            $this->_between_style_operators
5874
-        );
5875
-    }
5876
-
5877
-    /**
5878
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
5879
-     * @return array keys are accepted strings, values are the SQL they are converted to
5880
-     */
5881
-    public function valid_like_style_operators()
5882
-    {
5883
-        return array_intersect(
5884
-            $this->valid_operators(),
5885
-            $this->_like_style_operators
5886
-        );
5887
-    }
5888
-
5889
-    /**
5890
-     * Gets the "in"-style operators
5891
-     * @return array keys are accepted strings, values are the SQL they are converted to
5892
-     */
5893
-    public function valid_in_style_operators()
5894
-    {
5895
-        return array_intersect(
5896
-            $this->valid_operators(),
5897
-            $this->_in_style_operators
5898
-        );
5899
-    }
5900
-
5901
-    /**
5902
-     * Gets the "null"-style operators (accept no arguments)
5903
-     * @return array keys are accepted strings, values are the SQL they are converted to
5904
-     */
5905
-    public function valid_null_style_operators()
5906
-    {
5907
-        return array_intersect(
5908
-            $this->valid_operators(),
5909
-            $this->_null_style_operators
5910
-        );
5911
-    }
5912
-
5913
-    /**
5914
-     * Gets an array where keys are the primary keys and values are their 'names'
5915
-     * (as determined by the model object's name() function, which is often overridden)
5916
-     *
5917
-     * @param array $query_params like get_all's
5918
-     * @return string[]
5919
-     * @throws EE_Error
5920
-     */
5921
-    public function get_all_names($query_params = array())
5922
-    {
5923
-        $objs = $this->get_all($query_params);
5924
-        $names = array();
5925
-        foreach ($objs as $obj) {
5926
-            $names[$obj->ID()] = $obj->name();
5927
-        }
5928
-        return $names;
5929
-    }
5930
-
5931
-
5932
-
5933
-    /**
5934
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
5935
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
5936
-     * this is duplicated effort and reduces efficiency) you would be better to use
5937
-     * array_keys() on $model_objects.
5938
-     *
5939
-     * @param \EE_Base_Class[] $model_objects
5940
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
5941
-     *                                               in the returned array
5942
-     * @return array
5943
-     * @throws EE_Error
5944
-     */
5945
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
5946
-    {
5947
-        if (! $this->has_primary_key_field()) {
5948
-            if (WP_DEBUG) {
5949
-                EE_Error::add_error(
5950
-                    __('Trying to get IDs from a model than has no primary key', 'event_espresso'),
5951
-                    __FILE__,
5952
-                    __FUNCTION__,
5953
-                    __LINE__
5954
-                );
5955
-            }
5956
-        }
5957
-        $IDs = array();
5958
-        foreach ($model_objects as $model_object) {
5959
-            $id = $model_object->ID();
5960
-            if (! $id) {
5961
-                if ($filter_out_empty_ids) {
5962
-                    continue;
5963
-                }
5964
-                if (WP_DEBUG) {
5965
-                    EE_Error::add_error(
5966
-                        __(
5967
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
5968
-                            'event_espresso'
5969
-                        ),
5970
-                        __FILE__,
5971
-                        __FUNCTION__,
5972
-                        __LINE__
5973
-                    );
5974
-                }
5975
-            }
5976
-            $IDs[] = $id;
5977
-        }
5978
-        return $IDs;
5979
-    }
5980
-
5981
-
5982
-
5983
-    /**
5984
-     * Returns the string used in capabilities relating to this model. If there
5985
-     * are no capabilities that relate to this model returns false
5986
-     *
5987
-     * @return string|false
5988
-     */
5989
-    public function cap_slug()
5990
-    {
5991
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
5992
-    }
5993
-
5994
-
5995
-
5996
-    /**
5997
-     * Returns the capability-restrictions array (@see EEM_Base::_cap_restrictions).
5998
-     * If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
5999
-     * only returns the cap restrictions array in that context (ie, the array
6000
-     * at that key)
6001
-     *
6002
-     * @param string $context
6003
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6004
-     * @throws EE_Error
6005
-     */
6006
-    public function cap_restrictions($context = EEM_Base::caps_read)
6007
-    {
6008
-        EEM_Base::verify_is_valid_cap_context($context);
6009
-        //check if we ought to run the restriction generator first
6010
-        if (
6011
-            isset($this->_cap_restriction_generators[$context])
6012
-            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6013
-            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6014
-        ) {
6015
-            $this->_cap_restrictions[$context] = array_merge(
6016
-                $this->_cap_restrictions[$context],
6017
-                $this->_cap_restriction_generators[$context]->generate_restrictions()
6018
-            );
6019
-        }
6020
-        //and make sure we've finalized the construction of each restriction
6021
-        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6022
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6023
-                $where_conditions_obj->_finalize_construct($this);
6024
-            }
6025
-        }
6026
-        return $this->_cap_restrictions[$context];
6027
-    }
6028
-
6029
-
6030
-
6031
-    /**
6032
-     * Indicating whether or not this model thinks its a wp core model
6033
-     *
6034
-     * @return boolean
6035
-     */
6036
-    public function is_wp_core_model()
6037
-    {
6038
-        return $this->_wp_core_model;
6039
-    }
6040
-
6041
-
6042
-
6043
-    /**
6044
-     * Gets all the caps that are missing which impose a restriction on
6045
-     * queries made in this context
6046
-     *
6047
-     * @param string $context one of EEM_Base::caps_ constants
6048
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6049
-     * @throws EE_Error
6050
-     */
6051
-    public function caps_missing($context = EEM_Base::caps_read)
6052
-    {
6053
-        $missing_caps = array();
6054
-        $cap_restrictions = $this->cap_restrictions($context);
6055
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6056
-            if (! EE_Capabilities::instance()
6057
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6058
-            ) {
6059
-                $missing_caps[$cap] = $restriction_if_no_cap;
6060
-            }
6061
-        }
6062
-        return $missing_caps;
6063
-    }
6064
-
6065
-
6066
-
6067
-    /**
6068
-     * Gets the mapping from capability contexts to action strings used in capability names
6069
-     *
6070
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6071
-     * one of 'read', 'edit', or 'delete'
6072
-     */
6073
-    public function cap_contexts_to_cap_action_map()
6074
-    {
6075
-        return apply_filters('FHEE__EEM_Base__cap_contexts_to_cap_action_map', $this->_cap_contexts_to_cap_action_map,
6076
-            $this);
6077
-    }
6078
-
6079
-
6080
-
6081
-    /**
6082
-     * Gets the action string for the specified capability context
6083
-     *
6084
-     * @param string $context
6085
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6086
-     * @throws EE_Error
6087
-     */
6088
-    public function cap_action_for_context($context)
6089
-    {
6090
-        $mapping = $this->cap_contexts_to_cap_action_map();
6091
-        if (isset($mapping[$context])) {
6092
-            return $mapping[$context];
6093
-        }
6094
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6095
-            return $action;
6096
-        }
6097
-        throw new EE_Error(
6098
-            sprintf(
6099
-                __('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6100
-                $context,
6101
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6102
-            )
6103
-        );
6104
-    }
6105
-
6106
-
6107
-
6108
-    /**
6109
-     * Returns all the capability contexts which are valid when querying models
6110
-     *
6111
-     * @return array
6112
-     */
6113
-    public static function valid_cap_contexts()
6114
-    {
6115
-        return apply_filters('FHEE__EEM_Base__valid_cap_contexts', array(
6116
-            self::caps_read,
6117
-            self::caps_read_admin,
6118
-            self::caps_edit,
6119
-            self::caps_delete,
6120
-        ));
6121
-    }
6122
-
6123
-
6124
-
6125
-    /**
6126
-     * Returns all valid options for 'default_where_conditions'
6127
-     *
6128
-     * @return array
6129
-     */
6130
-    public static function valid_default_where_conditions()
6131
-    {
6132
-        return array(
6133
-            EEM_Base::default_where_conditions_all,
6134
-            EEM_Base::default_where_conditions_this_only,
6135
-            EEM_Base::default_where_conditions_others_only,
6136
-            EEM_Base::default_where_conditions_minimum_all,
6137
-            EEM_Base::default_where_conditions_minimum_others,
6138
-            EEM_Base::default_where_conditions_none
6139
-        );
6140
-    }
6141
-
6142
-    // public static function default_where_conditions_full
6143
-    /**
6144
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6145
-     *
6146
-     * @param string $context
6147
-     * @return bool
6148
-     * @throws EE_Error
6149
-     */
6150
-    static public function verify_is_valid_cap_context($context)
6151
-    {
6152
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6153
-        if (in_array($context, $valid_cap_contexts)) {
6154
-            return true;
6155
-        }
6156
-        throw new EE_Error(
6157
-            sprintf(
6158
-                __(
6159
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6160
-                    'event_espresso'
6161
-                ),
6162
-                $context,
6163
-                'EEM_Base',
6164
-                implode(',', $valid_cap_contexts)
6165
-            )
6166
-        );
6167
-    }
6168
-
6169
-
6170
-
6171
-    /**
6172
-     * Clears all the models field caches. This is only useful when a sub-class
6173
-     * might have added a field or something and these caches might be invalidated
6174
-     */
6175
-    protected function _invalidate_field_caches()
6176
-    {
6177
-        $this->_cache_foreign_key_to_fields = array();
6178
-        $this->_cached_fields = null;
6179
-        $this->_cached_fields_non_db_only = null;
6180
-    }
6181
-
6182
-
6183
-
6184
-    /**
6185
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6186
-     * (eg "and", "or", "not").
6187
-     *
6188
-     * @return array
6189
-     */
6190
-    public function logic_query_param_keys()
6191
-    {
6192
-        return $this->_logic_query_param_keys;
6193
-    }
6194
-
6195
-
6196
-
6197
-    /**
6198
-     * Determines whether or not the where query param array key is for a logic query param.
6199
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6200
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6201
-     *
6202
-     * @param $query_param_key
6203
-     * @return bool
6204
-     */
6205
-    public function is_logic_query_param_key($query_param_key)
6206
-    {
6207
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6208
-            if ($query_param_key === $logic_query_param_key
6209
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6210
-            ) {
6211
-                return true;
6212
-            }
6213
-        }
6214
-        return false;
6215
-    }
3774
+		}
3775
+		return $null_friendly_where_conditions;
3776
+	}
3777
+
3778
+
3779
+
3780
+	/**
3781
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3782
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3783
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3784
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3785
+	 *
3786
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3787
+	 * @return array like EEM_Base::get_all's $query_params[0] (where conditions)
3788
+	 */
3789
+	private function _get_default_where_conditions($model_relation_path = null)
3790
+	{
3791
+		if ($this->_ignore_where_strategy) {
3792
+			return array();
3793
+		}
3794
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3795
+	}
3796
+
3797
+
3798
+
3799
+	/**
3800
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3801
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3802
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3803
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3804
+	 * Similar to _get_default_where_conditions
3805
+	 *
3806
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3807
+	 * @return array like EEM_Base::get_all's $query_params[0] (where conditions)
3808
+	 */
3809
+	protected function _get_minimum_where_conditions($model_relation_path = null)
3810
+	{
3811
+		if ($this->_ignore_where_strategy) {
3812
+			return array();
3813
+		}
3814
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3815
+	}
3816
+
3817
+
3818
+
3819
+	/**
3820
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3821
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3822
+	 *
3823
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
3824
+	 * @return string
3825
+	 * @throws EE_Error
3826
+	 */
3827
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3828
+	{
3829
+		$selects = $this->_get_columns_to_select_for_this_model();
3830
+		foreach (
3831
+			$model_query_info->get_model_names_included() as $model_relation_chain =>
3832
+			$name_of_other_model_included
3833
+		) {
3834
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3835
+			$other_model_selects = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3836
+			foreach ($other_model_selects as $key => $value) {
3837
+				$selects[] = $value;
3838
+			}
3839
+		}
3840
+		return implode(", ", $selects);
3841
+	}
3842
+
3843
+
3844
+
3845
+	/**
3846
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3847
+	 * So that's going to be the columns for all the fields on the model
3848
+	 *
3849
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
3850
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3851
+	 */
3852
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3853
+	{
3854
+		$fields = $this->field_settings();
3855
+		$selects = array();
3856
+		$table_alias_with_model_relation_chain_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_prefix($model_relation_chain,
3857
+			$this->get_this_model_name());
3858
+		foreach ($fields as $field_obj) {
3859
+			$selects[] = $table_alias_with_model_relation_chain_prefix
3860
+						 . $field_obj->get_table_alias()
3861
+						 . "."
3862
+						 . $field_obj->get_table_column()
3863
+						 . " AS '"
3864
+						 . $table_alias_with_model_relation_chain_prefix
3865
+						 . $field_obj->get_table_alias()
3866
+						 . "."
3867
+						 . $field_obj->get_table_column()
3868
+						 . "'";
3869
+		}
3870
+		//make sure we are also getting the PKs of each table
3871
+		$tables = $this->get_tables();
3872
+		if (count($tables) > 1) {
3873
+			foreach ($tables as $table_obj) {
3874
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3875
+									   . $table_obj->get_fully_qualified_pk_column();
3876
+				if (! in_array($qualified_pk_column, $selects)) {
3877
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3878
+				}
3879
+			}
3880
+		}
3881
+		return $selects;
3882
+	}
3883
+
3884
+
3885
+
3886
+	/**
3887
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3888
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3889
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3890
+	 * SQL for joining, and the data types
3891
+	 *
3892
+	 * @param null|string                 $original_query_param
3893
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
3894
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3895
+	 * @param    string                   $query_param_type     like Registration.Transaction.TXN_ID
3896
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3897
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
3898
+	 *                                                          or 'Registration's
3899
+	 * @param string                      $original_query_param what it originally was (eg
3900
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3901
+	 *                                                          matches $query_param
3902
+	 * @throws EE_Error
3903
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3904
+	 */
3905
+	private function _extract_related_model_info_from_query_param(
3906
+		$query_param,
3907
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
3908
+		$query_param_type,
3909
+		$original_query_param = null
3910
+	) {
3911
+		if ($original_query_param === null) {
3912
+			$original_query_param = $query_param;
3913
+		}
3914
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
3915
+		/** @var $allow_logic_query_params bool whether or not to allow logic_query_params like 'NOT','OR', or 'AND' */
3916
+		$allow_logic_query_params = in_array($query_param_type, array('where', 'having', 0, 'custom_selects'), true);
3917
+		$allow_fields = in_array(
3918
+			$query_param_type,
3919
+			array('where', 'having', 'order_by', 'group_by', 'order', 'custom_selects', 0),
3920
+			true
3921
+		);
3922
+		//check to see if we have a field on this model
3923
+		$this_model_fields = $this->field_settings(true);
3924
+		if (array_key_exists($query_param, $this_model_fields)) {
3925
+			if ($allow_fields) {
3926
+				return;
3927
+			}
3928
+			throw new EE_Error(
3929
+				sprintf(
3930
+					__(
3931
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
3932
+						"event_espresso"
3933
+					),
3934
+					$query_param, get_class($this), $query_param_type, $original_query_param
3935
+				)
3936
+			);
3937
+		}
3938
+		//check if this is a special logic query param
3939
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
3940
+			if ($allow_logic_query_params) {
3941
+				return;
3942
+			}
3943
+			throw new EE_Error(
3944
+				sprintf(
3945
+					__(
3946
+						'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
3947
+						'event_espresso'
3948
+					),
3949
+					implode('", "', $this->_logic_query_param_keys),
3950
+					$query_param,
3951
+					get_class($this),
3952
+					'<br />',
3953
+					"\t"
3954
+					. ' $passed_in_query_info = <pre>'
3955
+					. print_r($passed_in_query_info, true)
3956
+					. '</pre>'
3957
+					. "\n\t"
3958
+					. ' $query_param_type = '
3959
+					. $query_param_type
3960
+					. "\n\t"
3961
+					. ' $original_query_param = '
3962
+					. $original_query_param
3963
+				)
3964
+			);
3965
+		}
3966
+		//check if it's a custom selection
3967
+		if ($this->_custom_selections instanceof CustomSelects
3968
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
3969
+		) {
3970
+			return;
3971
+		}
3972
+		//check if has a model name at the beginning
3973
+		//and
3974
+		//check if it's a field on a related model
3975
+		if ($this->extractJoinModelFromQueryParams(
3976
+			$passed_in_query_info,
3977
+			$query_param,
3978
+			$original_query_param,
3979
+			$query_param_type
3980
+		)) {
3981
+			return;
3982
+		}
3983
+
3984
+		//ok so $query_param didn't start with a model name
3985
+		//and we previously confirmed it wasn't a logic query param or field on the current model
3986
+		//it's wack, that's what it is
3987
+		throw new EE_Error(
3988
+			sprintf(
3989
+				esc_html__(
3990
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
3991
+					"event_espresso"
3992
+				),
3993
+				$query_param,
3994
+				get_class($this),
3995
+				$query_param_type,
3996
+				$original_query_param
3997
+			)
3998
+		);
3999
+	}
4000
+
4001
+
4002
+	/**
4003
+	 * Extracts any possible join model information from the provided possible_join_string.
4004
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model join
4005
+	 * parts that should be added to the query.
4006
+	 *
4007
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4008
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4009
+	 * @param null|string                 $original_query_param
4010
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4011
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects' etc.)
4012
+	 * @return bool  returns true if a join was added and false if not.
4013
+	 * @throws EE_Error
4014
+	 */
4015
+	private function extractJoinModelFromQueryParams(
4016
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4017
+		$possible_join_string,
4018
+		$original_query_param,
4019
+		$query_parameter_type
4020
+	) {
4021
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4022
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4023
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4024
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4025
+				if ($possible_join_string === '') {
4026
+					//nothing left to $query_param
4027
+					//we should actually end in a field name, not a model like this!
4028
+					throw new EE_Error(
4029
+						sprintf(
4030
+							esc_html__(
4031
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4032
+								"event_espresso"
4033
+							),
4034
+							$possible_join_string,
4035
+							$query_parameter_type,
4036
+							get_class($this),
4037
+							$valid_related_model_name
4038
+						)
4039
+					);
4040
+				}
4041
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4042
+				$related_model_obj->_extract_related_model_info_from_query_param(
4043
+					$possible_join_string,
4044
+					$query_info_carrier,
4045
+					$query_parameter_type,
4046
+					$original_query_param
4047
+				);
4048
+				return true;
4049
+			}
4050
+			if ($possible_join_string === $valid_related_model_name) {
4051
+				$this->_add_join_to_model(
4052
+					$valid_related_model_name,
4053
+					$query_info_carrier,
4054
+					$original_query_param
4055
+				);
4056
+				return true;
4057
+			}
4058
+		}
4059
+		return false;
4060
+	}
4061
+
4062
+
4063
+	/**
4064
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4065
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4066
+	 * @throws EE_Error
4067
+	 */
4068
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4069
+	{
4070
+		if ($this->_custom_selections instanceof CustomSelects
4071
+			&& ($this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4072
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4073
+			)
4074
+		) {
4075
+			$original_selects = $this->_custom_selections->originalSelects();
4076
+			foreach ($original_selects as $alias => $select_configuration) {
4077
+				$this->extractJoinModelFromQueryParams(
4078
+					$query_info_carrier,
4079
+					$select_configuration[0],
4080
+					$select_configuration[0],
4081
+					'custom_selects'
4082
+				);
4083
+			}
4084
+		}
4085
+	}
4086
+
4087
+
4088
+
4089
+	/**
4090
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4091
+	 * and store it on $passed_in_query_info
4092
+	 *
4093
+	 * @param string                      $model_name
4094
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4095
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4096
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4097
+	 *                                                          and are adding a join to 'Payment' with the original
4098
+	 *                                                          query param key
4099
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4100
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4101
+	 *                                                          Payment wants to add default query params so that it
4102
+	 *                                                          will know what models to prepend onto its default query
4103
+	 *                                                          params or in case it wants to rename tables (in case
4104
+	 *                                                          there are multiple joins to the same table)
4105
+	 * @return void
4106
+	 * @throws EE_Error
4107
+	 */
4108
+	private function _add_join_to_model(
4109
+		$model_name,
4110
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4111
+		$original_query_param
4112
+	) {
4113
+		$relation_obj = $this->related_settings_for($model_name);
4114
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4115
+		//check if the relation is HABTM, because then we're essentially doing two joins
4116
+		//If so, join first to the JOIN table, and add its data types, and then continue as normal
4117
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4118
+			$join_model_obj = $relation_obj->get_join_model();
4119
+			//replace the model specified with the join model for this relation chain, whi
4120
+			$relation_chain_to_join_model = EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain($model_name,
4121
+				$join_model_obj->get_this_model_name(), $model_relation_chain);
4122
+			$passed_in_query_info->merge(
4123
+				new EE_Model_Query_Info_Carrier(
4124
+					array($relation_chain_to_join_model => $join_model_obj->get_this_model_name()),
4125
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4126
+				)
4127
+			);
4128
+		}
4129
+		//now just join to the other table pointed to by the relation object, and add its data types
4130
+		$passed_in_query_info->merge(
4131
+			new EE_Model_Query_Info_Carrier(
4132
+				array($model_relation_chain => $model_name),
4133
+				$relation_obj->get_join_statement($model_relation_chain)
4134
+			)
4135
+		);
4136
+	}
4137
+
4138
+
4139
+
4140
+	/**
4141
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4142
+	 *
4143
+	 * @param array $where_params like EEM_Base::get_all
4144
+	 * @return string of SQL
4145
+	 * @throws EE_Error
4146
+	 */
4147
+	private function _construct_where_clause($where_params)
4148
+	{
4149
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4150
+		if ($SQL) {
4151
+			return " WHERE " . $SQL;
4152
+		}
4153
+		return '';
4154
+	}
4155
+
4156
+
4157
+
4158
+	/**
4159
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4160
+	 * and should be passed HAVING parameters, not WHERE parameters
4161
+	 *
4162
+	 * @param array $having_params
4163
+	 * @return string
4164
+	 * @throws EE_Error
4165
+	 */
4166
+	private function _construct_having_clause($having_params)
4167
+	{
4168
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4169
+		if ($SQL) {
4170
+			return " HAVING " . $SQL;
4171
+		}
4172
+		return '';
4173
+	}
4174
+
4175
+
4176
+	/**
4177
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4178
+	 * Event_Meta.meta_value = 'foo'))"
4179
+	 *
4180
+	 * @param array  $where_params see EEM_Base::get_all for documentation
4181
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4182
+	 * @throws EE_Error
4183
+	 * @return string of SQL
4184
+	 */
4185
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4186
+	{
4187
+		$where_clauses = array();
4188
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4189
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);//str_replace("*",'',$query_param);
4190
+			if (in_array($query_param, $this->_logic_query_param_keys)) {
4191
+				switch ($query_param) {
4192
+					case 'not':
4193
+					case 'NOT':
4194
+						$where_clauses[] = "! ("
4195
+										   . $this->_construct_condition_clause_recursive($op_and_value_or_sub_condition,
4196
+								$glue)
4197
+										   . ")";
4198
+						break;
4199
+					case 'and':
4200
+					case 'AND':
4201
+						$where_clauses[] = " ("
4202
+										   . $this->_construct_condition_clause_recursive($op_and_value_or_sub_condition,
4203
+								' AND ')
4204
+										   . ")";
4205
+						break;
4206
+					case 'or':
4207
+					case 'OR':
4208
+						$where_clauses[] = " ("
4209
+										   . $this->_construct_condition_clause_recursive($op_and_value_or_sub_condition,
4210
+								' OR ')
4211
+										   . ")";
4212
+						break;
4213
+				}
4214
+			} else {
4215
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4216
+				//if it's not a normal field, maybe it's a custom selection?
4217
+				if (! $field_obj) {
4218
+					if ($this->_custom_selections instanceof CustomSelects) {
4219
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4220
+					} else {
4221
+						throw new EE_Error(sprintf(__("%s is neither a valid model field name, nor a custom selection",
4222
+							"event_espresso"), $query_param));
4223
+					}
4224
+				}
4225
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4226
+				$where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4227
+			}
4228
+		}
4229
+		return $where_clauses ? implode($glue, $where_clauses) : '';
4230
+	}
4231
+
4232
+
4233
+
4234
+	/**
4235
+	 * Takes the input parameter and extract the table name (alias) and column name
4236
+	 *
4237
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4238
+	 * @throws EE_Error
4239
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4240
+	 */
4241
+	private function _deduce_column_name_from_query_param($query_param)
4242
+	{
4243
+		$field = $this->_deduce_field_from_query_param($query_param);
4244
+		if ($field) {
4245
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param($field->get_model_name(),
4246
+				$query_param);
4247
+			return $table_alias_prefix . $field->get_qualified_column();
4248
+		}
4249
+		if ($this->_custom_selections instanceof CustomSelects
4250
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4251
+		) {
4252
+			//maybe it's custom selection item?
4253
+			//if so, just use it as the "column name"
4254
+			return $query_param;
4255
+		}
4256
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4257
+			? implode(',', $this->_custom_selections->columnAliases())
4258
+			: '';
4259
+		throw new EE_Error(
4260
+			sprintf(
4261
+				__(
4262
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4263
+					"event_espresso"
4264
+				), $query_param, $custom_select_aliases
4265
+			)
4266
+		);
4267
+	}
4268
+
4269
+
4270
+
4271
+	/**
4272
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4273
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4274
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4275
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4276
+	 *
4277
+	 * @param string $condition_query_param_key
4278
+	 * @return string
4279
+	 */
4280
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4281
+	{
4282
+		$pos_of_star = strpos($condition_query_param_key, '*');
4283
+		if ($pos_of_star === false) {
4284
+			return $condition_query_param_key;
4285
+		}
4286
+		$condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4287
+		return $condition_query_param_sans_star;
4288
+	}
4289
+
4290
+
4291
+
4292
+	/**
4293
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4294
+	 *
4295
+	 * @param                            mixed      array | string    $op_and_value
4296
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4297
+	 * @throws EE_Error
4298
+	 * @return string
4299
+	 */
4300
+	private function _construct_op_and_value($op_and_value, $field_obj)
4301
+	{
4302
+		if (is_array($op_and_value)) {
4303
+			$operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4304
+			if (! $operator) {
4305
+				$php_array_like_string = array();
4306
+				foreach ($op_and_value as $key => $value) {
4307
+					$php_array_like_string[] = "$key=>$value";
4308
+				}
4309
+				throw new EE_Error(
4310
+					sprintf(
4311
+						__(
4312
+							"You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4313
+							"event_espresso"
4314
+						),
4315
+						implode(",", $php_array_like_string)
4316
+					)
4317
+				);
4318
+			}
4319
+			$value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4320
+		} else {
4321
+			$operator = '=';
4322
+			$value = $op_and_value;
4323
+		}
4324
+		//check to see if the value is actually another field
4325
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4326
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4327
+		}
4328
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4329
+			//in this case, the value should be an array, or at least a comma-separated list
4330
+			//it will need to handle a little differently
4331
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4332
+			//note: $cleaned_value has already been run through $wpdb->prepare()
4333
+			return $operator . SP . $cleaned_value;
4334
+		}
4335
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4336
+			//the value should be an array with count of two.
4337
+			if (count($value) !== 2) {
4338
+				throw new EE_Error(
4339
+					sprintf(
4340
+						__(
4341
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4342
+							'event_espresso'
4343
+						),
4344
+						"BETWEEN"
4345
+					)
4346
+				);
4347
+			}
4348
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4349
+			return $operator . SP . $cleaned_value;
4350
+		}
4351
+		if (in_array($operator, $this->valid_null_style_operators())) {
4352
+			if ($value !== null) {
4353
+				throw new EE_Error(
4354
+					sprintf(
4355
+						__(
4356
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4357
+							"event_espresso"
4358
+						),
4359
+						$value,
4360
+						$operator
4361
+					)
4362
+				);
4363
+			}
4364
+			return $operator;
4365
+		}
4366
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4367
+			//if the operator is 'LIKE', we want to allow percent signs (%) and not
4368
+			//remove other junk. So just treat it as a string.
4369
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4370
+		}
4371
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4372
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4373
+		}
4374
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4375
+			throw new EE_Error(
4376
+				sprintf(
4377
+					__(
4378
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4379
+						'event_espresso'
4380
+					),
4381
+					$operator,
4382
+					$operator
4383
+				)
4384
+			);
4385
+		}
4386
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4387
+			throw new EE_Error(
4388
+				sprintf(
4389
+					__(
4390
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4391
+						'event_espresso'
4392
+					),
4393
+					$operator,
4394
+					$operator
4395
+				)
4396
+			);
4397
+		}
4398
+		throw new EE_Error(
4399
+			sprintf(
4400
+				__(
4401
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4402
+					"event_espresso"
4403
+				),
4404
+				http_build_query($op_and_value)
4405
+			)
4406
+		);
4407
+	}
4408
+
4409
+
4410
+
4411
+	/**
4412
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4413
+	 *
4414
+	 * @param array                      $values
4415
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4416
+	 *                                              '%s'
4417
+	 * @return string
4418
+	 * @throws EE_Error
4419
+	 */
4420
+	public function _construct_between_value($values, $field_obj)
4421
+	{
4422
+		$cleaned_values = array();
4423
+		foreach ($values as $value) {
4424
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4425
+		}
4426
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4427
+	}
4428
+
4429
+
4430
+
4431
+	/**
4432
+	 * Takes an array or a comma-separated list of $values and cleans them
4433
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4434
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4435
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4436
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4437
+	 *
4438
+	 * @param mixed                      $values    array or comma-separated string
4439
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4440
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4441
+	 * @throws EE_Error
4442
+	 */
4443
+	public function _construct_in_value($values, $field_obj)
4444
+	{
4445
+		//check if the value is a CSV list
4446
+		if (is_string($values)) {
4447
+			//in which case, turn it into an array
4448
+			$values = explode(",", $values);
4449
+		}
4450
+		$cleaned_values = array();
4451
+		foreach ($values as $value) {
4452
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4453
+		}
4454
+		//we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4455
+		//but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4456
+		//which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4457
+		if (empty($cleaned_values)) {
4458
+			$all_fields = $this->field_settings();
4459
+			$a_field = array_shift($all_fields);
4460
+			$main_table = $this->_get_main_table();
4461
+			$cleaned_values[] = "SELECT "
4462
+								. $a_field->get_table_column()
4463
+								. " FROM "
4464
+								. $main_table->get_table_name()
4465
+								. " WHERE FALSE";
4466
+		}
4467
+		return "(" . implode(",", $cleaned_values) . ")";
4468
+	}
4469
+
4470
+
4471
+
4472
+	/**
4473
+	 * @param mixed                      $value
4474
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4475
+	 * @throws EE_Error
4476
+	 * @return false|null|string
4477
+	 */
4478
+	private function _wpdb_prepare_using_field($value, $field_obj)
4479
+	{
4480
+		/** @type WPDB $wpdb */
4481
+		global $wpdb;
4482
+		if ($field_obj instanceof EE_Model_Field_Base) {
4483
+			return $wpdb->prepare($field_obj->get_wpdb_data_type(),
4484
+				$this->_prepare_value_for_use_in_db($value, $field_obj));
4485
+		} //$field_obj should really just be a data type
4486
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4487
+			throw new EE_Error(
4488
+				sprintf(
4489
+					__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4490
+					$field_obj, implode(",", $this->_valid_wpdb_data_types)
4491
+				)
4492
+			);
4493
+		}
4494
+		return $wpdb->prepare($field_obj, $value);
4495
+	}
4496
+
4497
+
4498
+
4499
+	/**
4500
+	 * Takes the input parameter and finds the model field that it indicates.
4501
+	 *
4502
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4503
+	 * @throws EE_Error
4504
+	 * @return EE_Model_Field_Base
4505
+	 */
4506
+	protected function _deduce_field_from_query_param($query_param_name)
4507
+	{
4508
+		//ok, now proceed with deducing which part is the model's name, and which is the field's name
4509
+		//which will help us find the database table and column
4510
+		$query_param_parts = explode(".", $query_param_name);
4511
+		if (empty($query_param_parts)) {
4512
+			throw new EE_Error(sprintf(__("_extract_column_name is empty when trying to extract column and table name from %s",
4513
+				'event_espresso'), $query_param_name));
4514
+		}
4515
+		$number_of_parts = count($query_param_parts);
4516
+		$last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4517
+		if ($number_of_parts === 1) {
4518
+			$field_name = $last_query_param_part;
4519
+			$model_obj = $this;
4520
+		} else {// $number_of_parts >= 2
4521
+			//the last part is the column name, and there are only 2parts. therefore...
4522
+			$field_name = $last_query_param_part;
4523
+			$model_obj = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4524
+		}
4525
+		try {
4526
+			return $model_obj->field_settings_for($field_name);
4527
+		} catch (EE_Error $e) {
4528
+			return null;
4529
+		}
4530
+	}
4531
+
4532
+
4533
+
4534
+	/**
4535
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4536
+	 * alias and column which corresponds to it
4537
+	 *
4538
+	 * @param string $field_name
4539
+	 * @throws EE_Error
4540
+	 * @return string
4541
+	 */
4542
+	public function _get_qualified_column_for_field($field_name)
4543
+	{
4544
+		$all_fields = $this->field_settings();
4545
+		$field = isset($all_fields[$field_name]) ? $all_fields[$field_name] : false;
4546
+		if ($field) {
4547
+			return $field->get_qualified_column();
4548
+		}
4549
+		throw new EE_Error(
4550
+			sprintf(
4551
+				__(
4552
+					"There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4553
+					'event_espresso'
4554
+				), $field_name, get_class($this)
4555
+			)
4556
+		);
4557
+	}
4558
+
4559
+
4560
+
4561
+	/**
4562
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4563
+	 * Example usage:
4564
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4565
+	 *      array(),
4566
+	 *      ARRAY_A,
4567
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4568
+	 *  );
4569
+	 * is equivalent to
4570
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4571
+	 * and
4572
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4573
+	 *      array(
4574
+	 *          array(
4575
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4576
+	 *          ),
4577
+	 *          ARRAY_A,
4578
+	 *          implode(
4579
+	 *              ', ',
4580
+	 *              array_merge(
4581
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4582
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4583
+	 *              )
4584
+	 *          )
4585
+	 *      )
4586
+	 *  );
4587
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4588
+	 *
4589
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4590
+	 *                                            and the one whose fields you are selecting for example: when querying
4591
+	 *                                            tickets model and selecting fields from the tickets model you would
4592
+	 *                                            leave this parameter empty, because no models are needed to join
4593
+	 *                                            between the queried model and the selected one. Likewise when
4594
+	 *                                            querying the datetime model and selecting fields from the tickets
4595
+	 *                                            model, it would also be left empty, because there is a direct
4596
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4597
+	 *                                            them together. However, when querying from the event model and
4598
+	 *                                            selecting fields from the ticket model, you should provide the string
4599
+	 *                                            'Datetime', indicating that the event model must first join to the
4600
+	 *                                            datetime model in order to find its relation to ticket model.
4601
+	 *                                            Also, when querying from the venue model and selecting fields from
4602
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4603
+	 *                                            indicating you need to join the venue model to the event model,
4604
+	 *                                            to the datetime model, in order to find its relation to the ticket model.
4605
+	 *                                            This string is used to deduce the prefix that gets added onto the
4606
+	 *                                            models' tables qualified columns
4607
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4608
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4609
+	 *                                            qualified column names
4610
+	 * @return array|string
4611
+	 */
4612
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4613
+	{
4614
+		$table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4615
+		$qualified_columns = array();
4616
+		foreach ($this->field_settings() as $field_name => $field) {
4617
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4618
+		}
4619
+		return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4620
+	}
4621
+
4622
+
4623
+
4624
+	/**
4625
+	 * constructs the select use on special limit joins
4626
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4627
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4628
+	 * (as that is typically where the limits would be set).
4629
+	 *
4630
+	 * @param  string       $table_alias The table the select is being built for
4631
+	 * @param  mixed|string $limit       The limit for this select
4632
+	 * @return string                The final select join element for the query.
4633
+	 */
4634
+	public function _construct_limit_join_select($table_alias, $limit)
4635
+	{
4636
+		$SQL = '';
4637
+		foreach ($this->_tables as $table_obj) {
4638
+			if ($table_obj instanceof EE_Primary_Table) {
4639
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4640
+					? $table_obj->get_select_join_limit($limit)
4641
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4642
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4643
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4644
+					? $table_obj->get_select_join_limit_join($limit)
4645
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4646
+			}
4647
+		}
4648
+		return $SQL;
4649
+	}
4650
+
4651
+
4652
+
4653
+	/**
4654
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4655
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4656
+	 *
4657
+	 * @return string SQL
4658
+	 * @throws EE_Error
4659
+	 */
4660
+	public function _construct_internal_join()
4661
+	{
4662
+		$SQL = $this->_get_main_table()->get_table_sql();
4663
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4664
+		return $SQL;
4665
+	}
4666
+
4667
+
4668
+
4669
+	/**
4670
+	 * Constructs the SQL for joining all the tables on this model.
4671
+	 * Normally $alias should be the primary table's alias, but in cases where
4672
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4673
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4674
+	 * alias, this will construct SQL like:
4675
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4676
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4677
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4678
+	 *
4679
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4680
+	 * @return string
4681
+	 */
4682
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4683
+	{
4684
+		$SQL = '';
4685
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4686
+		foreach ($this->_tables as $table_obj) {
4687
+			if ($table_obj instanceof EE_Secondary_Table) {//table is secondary table
4688
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4689
+					//so we're joining to this table, meaning the table is already in
4690
+					//the FROM statement, BUT the primary table isn't. So we want
4691
+					//to add the inverse join sql
4692
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4693
+				} else {
4694
+					//just add a regular JOIN to this table from the primary table
4695
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4696
+				}
4697
+			}//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4698
+		}
4699
+		return $SQL;
4700
+	}
4701
+
4702
+
4703
+
4704
+	/**
4705
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4706
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4707
+	 * their data type (eg, '%s', '%d', etc)
4708
+	 *
4709
+	 * @return array
4710
+	 */
4711
+	public function _get_data_types()
4712
+	{
4713
+		$data_types = array();
4714
+		foreach ($this->field_settings() as $field_obj) {
4715
+			//$data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4716
+			/** @var $field_obj EE_Model_Field_Base */
4717
+			$data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4718
+		}
4719
+		return $data_types;
4720
+	}
4721
+
4722
+
4723
+
4724
+	/**
4725
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4726
+	 *
4727
+	 * @param string $model_name
4728
+	 * @throws EE_Error
4729
+	 * @return EEM_Base
4730
+	 */
4731
+	public function get_related_model_obj($model_name)
4732
+	{
4733
+		$model_classname = "EEM_" . $model_name;
4734
+		if (! class_exists($model_classname)) {
4735
+			throw new EE_Error(sprintf(__("You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4736
+				'event_espresso'), $model_name, $model_classname));
4737
+		}
4738
+		return call_user_func($model_classname . "::instance");
4739
+	}
4740
+
4741
+
4742
+
4743
+	/**
4744
+	 * Returns the array of EE_ModelRelations for this model.
4745
+	 *
4746
+	 * @return EE_Model_Relation_Base[]
4747
+	 */
4748
+	public function relation_settings()
4749
+	{
4750
+		return $this->_model_relations;
4751
+	}
4752
+
4753
+
4754
+
4755
+	/**
4756
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4757
+	 * because without THOSE models, this model probably doesn't have much purpose.
4758
+	 * (Eg, without an event, datetimes have little purpose.)
4759
+	 *
4760
+	 * @return EE_Belongs_To_Relation[]
4761
+	 */
4762
+	public function belongs_to_relations()
4763
+	{
4764
+		$belongs_to_relations = array();
4765
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4766
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
4767
+				$belongs_to_relations[$model_name] = $relation_obj;
4768
+			}
4769
+		}
4770
+		return $belongs_to_relations;
4771
+	}
4772
+
4773
+
4774
+
4775
+	/**
4776
+	 * Returns the specified EE_Model_Relation, or throws an exception
4777
+	 *
4778
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
4779
+	 * @throws EE_Error
4780
+	 * @return EE_Model_Relation_Base
4781
+	 */
4782
+	public function related_settings_for($relation_name)
4783
+	{
4784
+		$relatedModels = $this->relation_settings();
4785
+		if (! array_key_exists($relation_name, $relatedModels)) {
4786
+			throw new EE_Error(
4787
+				sprintf(
4788
+					__('Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4789
+						'event_espresso'),
4790
+					$relation_name,
4791
+					$this->_get_class_name(),
4792
+					implode(', ', array_keys($relatedModels))
4793
+				)
4794
+			);
4795
+		}
4796
+		return $relatedModels[$relation_name];
4797
+	}
4798
+
4799
+
4800
+
4801
+	/**
4802
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4803
+	 * fields
4804
+	 *
4805
+	 * @param string $fieldName
4806
+	 * @param boolean $include_db_only_fields
4807
+	 * @throws EE_Error
4808
+	 * @return EE_Model_Field_Base
4809
+	 */
4810
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
4811
+	{
4812
+		$fieldSettings = $this->field_settings($include_db_only_fields);
4813
+		if (! array_key_exists($fieldName, $fieldSettings)) {
4814
+			throw new EE_Error(sprintf(__("There is no field/column '%s' on '%s'", 'event_espresso'), $fieldName,
4815
+				get_class($this)));
4816
+		}
4817
+		return $fieldSettings[$fieldName];
4818
+	}
4819
+
4820
+
4821
+
4822
+	/**
4823
+	 * Checks if this field exists on this model
4824
+	 *
4825
+	 * @param string $fieldName a key in the model's _field_settings array
4826
+	 * @return boolean
4827
+	 */
4828
+	public function has_field($fieldName)
4829
+	{
4830
+		$fieldSettings = $this->field_settings(true);
4831
+		if (isset($fieldSettings[$fieldName])) {
4832
+			return true;
4833
+		}
4834
+		return false;
4835
+	}
4836
+
4837
+
4838
+
4839
+	/**
4840
+	 * Returns whether or not this model has a relation to the specified model
4841
+	 *
4842
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
4843
+	 * @return boolean
4844
+	 */
4845
+	public function has_relation($relation_name)
4846
+	{
4847
+		$relations = $this->relation_settings();
4848
+		if (isset($relations[$relation_name])) {
4849
+			return true;
4850
+		}
4851
+		return false;
4852
+	}
4853
+
4854
+
4855
+
4856
+	/**
4857
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4858
+	 * Eg, on EE_Answer that would be ANS_ID field object
4859
+	 *
4860
+	 * @param $field_obj
4861
+	 * @return boolean
4862
+	 */
4863
+	public function is_primary_key_field($field_obj)
4864
+	{
4865
+		return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
4866
+	}
4867
+
4868
+
4869
+
4870
+	/**
4871
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4872
+	 * Eg, on EE_Answer that would be ANS_ID field object
4873
+	 *
4874
+	 * @return EE_Model_Field_Base
4875
+	 * @throws EE_Error
4876
+	 */
4877
+	public function get_primary_key_field()
4878
+	{
4879
+		if ($this->_primary_key_field === null) {
4880
+			foreach ($this->field_settings(true) as $field_obj) {
4881
+				if ($this->is_primary_key_field($field_obj)) {
4882
+					$this->_primary_key_field = $field_obj;
4883
+					break;
4884
+				}
4885
+			}
4886
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
4887
+				throw new EE_Error(sprintf(__("There is no Primary Key defined on model %s", 'event_espresso'),
4888
+					get_class($this)));
4889
+			}
4890
+		}
4891
+		return $this->_primary_key_field;
4892
+	}
4893
+
4894
+
4895
+
4896
+	/**
4897
+	 * Returns whether or not not there is a primary key on this model.
4898
+	 * Internally does some caching.
4899
+	 *
4900
+	 * @return boolean
4901
+	 */
4902
+	public function has_primary_key_field()
4903
+	{
4904
+		if ($this->_has_primary_key_field === null) {
4905
+			try {
4906
+				$this->get_primary_key_field();
4907
+				$this->_has_primary_key_field = true;
4908
+			} catch (EE_Error $e) {
4909
+				$this->_has_primary_key_field = false;
4910
+			}
4911
+		}
4912
+		return $this->_has_primary_key_field;
4913
+	}
4914
+
4915
+
4916
+
4917
+	/**
4918
+	 * Finds the first field of type $field_class_name.
4919
+	 *
4920
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
4921
+	 *                                 EE_Foreign_Key_Field, etc
4922
+	 * @return EE_Model_Field_Base or null if none is found
4923
+	 */
4924
+	public function get_a_field_of_type($field_class_name)
4925
+	{
4926
+		foreach ($this->field_settings() as $field) {
4927
+			if ($field instanceof $field_class_name) {
4928
+				return $field;
4929
+			}
4930
+		}
4931
+		return null;
4932
+	}
4933
+
4934
+
4935
+
4936
+	/**
4937
+	 * Gets a foreign key field pointing to model.
4938
+	 *
4939
+	 * @param string $model_name eg Event, Registration, not EEM_Event
4940
+	 * @return EE_Foreign_Key_Field_Base
4941
+	 * @throws EE_Error
4942
+	 */
4943
+	public function get_foreign_key_to($model_name)
4944
+	{
4945
+		if (! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4946
+			foreach ($this->field_settings() as $field) {
4947
+				if (
4948
+					$field instanceof EE_Foreign_Key_Field_Base
4949
+					&& in_array($model_name, $field->get_model_names_pointed_to())
4950
+				) {
4951
+					$this->_cache_foreign_key_to_fields[$model_name] = $field;
4952
+					break;
4953
+				}
4954
+			}
4955
+			if (! isset($this->_cache_foreign_key_to_fields[$model_name])) {
4956
+				throw new EE_Error(sprintf(__("There is no foreign key field pointing to model %s on model %s",
4957
+					'event_espresso'), $model_name, get_class($this)));
4958
+			}
4959
+		}
4960
+		return $this->_cache_foreign_key_to_fields[$model_name];
4961
+	}
4962
+
4963
+
4964
+
4965
+	/**
4966
+	 * Gets the table name (including $wpdb->prefix) for the table alias
4967
+	 *
4968
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
4969
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
4970
+	 *                            Either one works
4971
+	 * @return string
4972
+	 */
4973
+	public function get_table_for_alias($table_alias)
4974
+	{
4975
+		$table_alias_sans_model_relation_chain_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
4976
+		return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
4977
+	}
4978
+
4979
+
4980
+
4981
+	/**
4982
+	 * Returns a flat array of all field son this model, instead of organizing them
4983
+	 * by table_alias as they are in the constructor.
4984
+	 *
4985
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
4986
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
4987
+	 */
4988
+	public function field_settings($include_db_only_fields = false)
4989
+	{
4990
+		if ($include_db_only_fields) {
4991
+			if ($this->_cached_fields === null) {
4992
+				$this->_cached_fields = array();
4993
+				foreach ($this->_fields as $fields_corresponding_to_table) {
4994
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
4995
+						$this->_cached_fields[$field_name] = $field_obj;
4996
+					}
4997
+				}
4998
+			}
4999
+			return $this->_cached_fields;
5000
+		}
5001
+		if ($this->_cached_fields_non_db_only === null) {
5002
+			$this->_cached_fields_non_db_only = array();
5003
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5004
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5005
+					/** @var $field_obj EE_Model_Field_Base */
5006
+					if (! $field_obj->is_db_only_field()) {
5007
+						$this->_cached_fields_non_db_only[$field_name] = $field_obj;
5008
+					}
5009
+				}
5010
+			}
5011
+		}
5012
+		return $this->_cached_fields_non_db_only;
5013
+	}
5014
+
5015
+
5016
+
5017
+	/**
5018
+	 *        cycle though array of attendees and create objects out of each item
5019
+	 *
5020
+	 * @access        private
5021
+	 * @param        array $rows of results of $wpdb->get_results($query,ARRAY_A)
5022
+	 * @return \EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5023
+	 *                           numerically indexed)
5024
+	 * @throws EE_Error
5025
+	 */
5026
+	protected function _create_objects($rows = array())
5027
+	{
5028
+		$array_of_objects = array();
5029
+		if (empty($rows)) {
5030
+			return array();
5031
+		}
5032
+		$count_if_model_has_no_primary_key = 0;
5033
+		$has_primary_key = $this->has_primary_key_field();
5034
+		$primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5035
+		foreach ((array)$rows as $row) {
5036
+			if (empty($row)) {
5037
+				//wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5038
+				return array();
5039
+			}
5040
+			//check if we've already set this object in the results array,
5041
+			//in which case there's no need to process it further (again)
5042
+			if ($has_primary_key) {
5043
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5044
+					$row,
5045
+					$primary_key_field->get_qualified_column(),
5046
+					$primary_key_field->get_table_column()
5047
+				);
5048
+				if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5049
+					continue;
5050
+				}
5051
+			}
5052
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5053
+			if (! $classInstance) {
5054
+				throw new EE_Error(
5055
+					sprintf(
5056
+						__('Could not create instance of class %s from row %s', 'event_espresso'),
5057
+						$this->get_this_model_name(),
5058
+						http_build_query($row)
5059
+					)
5060
+				);
5061
+			}
5062
+			//set the timezone on the instantiated objects
5063
+			$classInstance->set_timezone($this->_timezone);
5064
+			//make sure if there is any timezone setting present that we set the timezone for the object
5065
+			$key = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5066
+			$array_of_objects[$key] = $classInstance;
5067
+			//also, for all the relations of type BelongsTo, see if we can cache
5068
+			//those related models
5069
+			//(we could do this for other relations too, but if there are conditions
5070
+			//that filtered out some fo the results, then we'd be caching an incomplete set
5071
+			//so it requires a little more thought than just caching them immediately...)
5072
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5073
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5074
+					//check if this model's INFO is present. If so, cache it on the model
5075
+					$other_model = $relation_obj->get_other_model();
5076
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5077
+					//if we managed to make a model object from the results, cache it on the main model object
5078
+					if ($other_model_obj_maybe) {
5079
+						//set timezone on these other model objects if they are present
5080
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5081
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5082
+					}
5083
+				}
5084
+			}
5085
+			//also, if this was a custom select query, let's see if there are any results for the custom select fields
5086
+			//and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5087
+			//the field in the CustomSelects object
5088
+			if ($this->_custom_selections instanceof CustomSelects) {
5089
+				$classInstance->setCustomSelectsValues(
5090
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5091
+				);
5092
+			}
5093
+		}
5094
+		return $array_of_objects;
5095
+	}
5096
+
5097
+
5098
+	/**
5099
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5100
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5101
+	 *
5102
+	 * @param array $db_results_row
5103
+	 * @return array
5104
+	 */
5105
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5106
+	{
5107
+		$results = array();
5108
+		if ($this->_custom_selections instanceof CustomSelects) {
5109
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5110
+				if (isset($db_results_row[$alias])) {
5111
+					$results[$alias] = $this->convertValueToDataType(
5112
+						$db_results_row[$alias],
5113
+						$this->_custom_selections->getDataTypeForAlias($alias)
5114
+					);
5115
+				}
5116
+			}
5117
+		}
5118
+		return $results;
5119
+	}
5120
+
5121
+
5122
+	/**
5123
+	 * This will set the value for the given alias
5124
+	 * @param string $value
5125
+	 * @param string $datatype (one of %d, %s, %f)
5126
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5127
+	 */
5128
+	protected function convertValueToDataType($value, $datatype)
5129
+	{
5130
+		switch ($datatype) {
5131
+			case '%f':
5132
+				return (float) $value;
5133
+			case '%d':
5134
+				return (int) $value;
5135
+			default:
5136
+				return (string) $value;
5137
+		}
5138
+	}
5139
+
5140
+
5141
+	/**
5142
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5143
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5144
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5145
+	 * object (as set in the model_field!).
5146
+	 *
5147
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5148
+	 */
5149
+	public function create_default_object()
5150
+	{
5151
+		$this_model_fields_and_values = array();
5152
+		//setup the row using default values;
5153
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5154
+			$this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5155
+		}
5156
+		$className = $this->_get_class_name();
5157
+		$classInstance = EE_Registry::instance()
5158
+									->load_class($className, array($this_model_fields_and_values), false, false);
5159
+		return $classInstance;
5160
+	}
5161
+
5162
+
5163
+
5164
+	/**
5165
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5166
+	 *                             or an stdClass where each property is the name of a column,
5167
+	 * @return EE_Base_Class
5168
+	 * @throws EE_Error
5169
+	 */
5170
+	public function instantiate_class_from_array_or_object($cols_n_values)
5171
+	{
5172
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5173
+			$cols_n_values = get_object_vars($cols_n_values);
5174
+		}
5175
+		$primary_key = null;
5176
+		//make sure the array only has keys that are fields/columns on this model
5177
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5178
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5179
+			$primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5180
+		}
5181
+		$className = $this->_get_class_name();
5182
+		//check we actually found results that we can use to build our model object
5183
+		//if not, return null
5184
+		if ($this->has_primary_key_field()) {
5185
+			if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5186
+				return null;
5187
+			}
5188
+		} else if ($this->unique_indexes()) {
5189
+			$first_column = reset($this_model_fields_n_values);
5190
+			if (empty($first_column)) {
5191
+				return null;
5192
+			}
5193
+		}
5194
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5195
+		if ($primary_key) {
5196
+			$classInstance = $this->get_from_entity_map($primary_key);
5197
+			if (! $classInstance) {
5198
+				$classInstance = EE_Registry::instance()
5199
+											->load_class($className,
5200
+												array($this_model_fields_n_values, $this->_timezone), true, false);
5201
+				// add this new object to the entity map
5202
+				$classInstance = $this->add_to_entity_map($classInstance);
5203
+			}
5204
+		} else {
5205
+			$classInstance = EE_Registry::instance()
5206
+										->load_class($className, array($this_model_fields_n_values, $this->_timezone),
5207
+											true, false);
5208
+		}
5209
+		return $classInstance;
5210
+	}
5211
+
5212
+
5213
+
5214
+	/**
5215
+	 * Gets the model object from the  entity map if it exists
5216
+	 *
5217
+	 * @param int|string $id the ID of the model object
5218
+	 * @return EE_Base_Class
5219
+	 */
5220
+	public function get_from_entity_map($id)
5221
+	{
5222
+		return isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])
5223
+			? $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] : null;
5224
+	}
5225
+
5226
+
5227
+
5228
+	/**
5229
+	 * add_to_entity_map
5230
+	 * Adds the object to the model's entity mappings
5231
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5232
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5233
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5234
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5235
+	 *        then this method should be called immediately after the update query
5236
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5237
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5238
+	 *
5239
+	 * @param    EE_Base_Class $object
5240
+	 * @throws EE_Error
5241
+	 * @return \EE_Base_Class
5242
+	 */
5243
+	public function add_to_entity_map(EE_Base_Class $object)
5244
+	{
5245
+		$className = $this->_get_class_name();
5246
+		if (! $object instanceof $className) {
5247
+			throw new EE_Error(sprintf(__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5248
+				is_object($object) ? get_class($object) : $object, $className));
5249
+		}
5250
+		/** @var $object EE_Base_Class */
5251
+		if (! $object->ID()) {
5252
+			throw new EE_Error(sprintf(__("You tried storing a model object with NO ID in the %s entity mapper.",
5253
+				"event_espresso"), get_class($this)));
5254
+		}
5255
+		// double check it's not already there
5256
+		$classInstance = $this->get_from_entity_map($object->ID());
5257
+		if ($classInstance) {
5258
+			return $classInstance;
5259
+		}
5260
+		$this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5261
+		return $object;
5262
+	}
5263
+
5264
+
5265
+
5266
+	/**
5267
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5268
+	 * if no identifier is provided, then the entire entity map is emptied
5269
+	 *
5270
+	 * @param int|string $id the ID of the model object
5271
+	 * @return boolean
5272
+	 */
5273
+	public function clear_entity_map($id = null)
5274
+	{
5275
+		if (empty($id)) {
5276
+			$this->_entity_map[EEM_Base::$_model_query_blog_id] = array();
5277
+			return true;
5278
+		}
5279
+		if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5280
+			unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5281
+			return true;
5282
+		}
5283
+		return false;
5284
+	}
5285
+
5286
+
5287
+
5288
+	/**
5289
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5290
+	 * Given an array where keys are column (or column alias) names and values,
5291
+	 * returns an array of their corresponding field names and database values
5292
+	 *
5293
+	 * @param array $cols_n_values
5294
+	 * @return array
5295
+	 */
5296
+	public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5297
+	{
5298
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5299
+	}
5300
+
5301
+
5302
+
5303
+	/**
5304
+	 * _deduce_fields_n_values_from_cols_n_values
5305
+	 * Given an array where keys are column (or column alias) names and values,
5306
+	 * returns an array of their corresponding field names and database values
5307
+	 *
5308
+	 * @param string $cols_n_values
5309
+	 * @return array
5310
+	 */
5311
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5312
+	{
5313
+		$this_model_fields_n_values = array();
5314
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5315
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not($cols_n_values,
5316
+				$table_obj->get_fully_qualified_pk_column(), $table_obj->get_pk_column());
5317
+			//there is a primary key on this table and its not set. Use defaults for all its columns
5318
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5319
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5320
+					if (! $field_obj->is_db_only_field()) {
5321
+						//prepare field as if its coming from db
5322
+						$prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5323
+						$this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5324
+					}
5325
+				}
5326
+			} else {
5327
+				//the table's rows existed. Use their values
5328
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5329
+					if (! $field_obj->is_db_only_field()) {
5330
+						$this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5331
+							$cols_n_values, $field_obj->get_qualified_column(),
5332
+							$field_obj->get_table_column()
5333
+						);
5334
+					}
5335
+				}
5336
+			}
5337
+		}
5338
+		return $this_model_fields_n_values;
5339
+	}
5340
+
5341
+
5342
+
5343
+	/**
5344
+	 * @param $cols_n_values
5345
+	 * @param $qualified_column
5346
+	 * @param $regular_column
5347
+	 * @return null
5348
+	 */
5349
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5350
+	{
5351
+		$value = null;
5352
+		//ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5353
+		//does the field on the model relate to this column retrieved from the db?
5354
+		//or is it a db-only field? (not relating to the model)
5355
+		if (isset($cols_n_values[$qualified_column])) {
5356
+			$value = $cols_n_values[$qualified_column];
5357
+		} elseif (isset($cols_n_values[$regular_column])) {
5358
+			$value = $cols_n_values[$regular_column];
5359
+		}
5360
+		return $value;
5361
+	}
5362
+
5363
+
5364
+
5365
+	/**
5366
+	 * refresh_entity_map_from_db
5367
+	 * Makes sure the model object in the entity map at $id assumes the values
5368
+	 * of the database (opposite of EE_base_Class::save())
5369
+	 *
5370
+	 * @param int|string $id
5371
+	 * @return EE_Base_Class
5372
+	 * @throws EE_Error
5373
+	 */
5374
+	public function refresh_entity_map_from_db($id)
5375
+	{
5376
+		$obj_in_map = $this->get_from_entity_map($id);
5377
+		if ($obj_in_map) {
5378
+			$wpdb_results = $this->_get_all_wpdb_results(
5379
+				array(array($this->get_primary_key_field()->get_name() => $id), 'limit' => 1)
5380
+			);
5381
+			if ($wpdb_results && is_array($wpdb_results)) {
5382
+				$one_row = reset($wpdb_results);
5383
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5384
+					$obj_in_map->set_from_db($field_name, $db_value);
5385
+				}
5386
+				//clear the cache of related model objects
5387
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5388
+					$obj_in_map->clear_cache($relation_name, null, true);
5389
+				}
5390
+			}
5391
+			$this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5392
+			return $obj_in_map;
5393
+		}
5394
+		return $this->get_one_by_ID($id);
5395
+	}
5396
+
5397
+
5398
+
5399
+	/**
5400
+	 * refresh_entity_map_with
5401
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5402
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5403
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5404
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5405
+	 *
5406
+	 * @param int|string    $id
5407
+	 * @param EE_Base_Class $replacing_model_obj
5408
+	 * @return \EE_Base_Class
5409
+	 * @throws EE_Error
5410
+	 */
5411
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5412
+	{
5413
+		$obj_in_map = $this->get_from_entity_map($id);
5414
+		if ($obj_in_map) {
5415
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5416
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5417
+					$obj_in_map->set($field_name, $value);
5418
+				}
5419
+				//make the model object in the entity map's cache match the $replacing_model_obj
5420
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5421
+					$obj_in_map->clear_cache($relation_name, null, true);
5422
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5423
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5424
+					}
5425
+				}
5426
+			}
5427
+			return $obj_in_map;
5428
+		}
5429
+		$this->add_to_entity_map($replacing_model_obj);
5430
+		return $replacing_model_obj;
5431
+	}
5432
+
5433
+
5434
+
5435
+	/**
5436
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5437
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5438
+	 * require_once($this->_getClassName().".class.php");
5439
+	 *
5440
+	 * @return string
5441
+	 */
5442
+	private function _get_class_name()
5443
+	{
5444
+		return "EE_" . $this->get_this_model_name();
5445
+	}
5446
+
5447
+
5448
+
5449
+	/**
5450
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5451
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5452
+	 * it would be 'Events'.
5453
+	 *
5454
+	 * @param int $quantity
5455
+	 * @return string
5456
+	 */
5457
+	public function item_name($quantity = 1)
5458
+	{
5459
+		return (int)$quantity === 1 ? $this->singular_item : $this->plural_item;
5460
+	}
5461
+
5462
+
5463
+
5464
+	/**
5465
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5466
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5467
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5468
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5469
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5470
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5471
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5472
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5473
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5474
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5475
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5476
+	 *        return $previousReturnValue.$returnString;
5477
+	 * }
5478
+	 * require('EEM_Answer.model.php');
5479
+	 * $answer=EEM_Answer::instance();
5480
+	 * echo $answer->my_callback('monkeys',100);
5481
+	 * //will output "you called my_callback! and passed args:monkeys,100"
5482
+	 *
5483
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5484
+	 * @param array  $args       array of original arguments passed to the function
5485
+	 * @throws EE_Error
5486
+	 * @return mixed whatever the plugin which calls add_filter decides
5487
+	 */
5488
+	public function __call($methodName, $args)
5489
+	{
5490
+		$className = get_class($this);
5491
+		$tagName = "FHEE__{$className}__{$methodName}";
5492
+		if (! has_filter($tagName)) {
5493
+			throw new EE_Error(
5494
+				sprintf(
5495
+					__('Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5496
+						'event_espresso'),
5497
+					$methodName,
5498
+					$className,
5499
+					$tagName,
5500
+					'<br />'
5501
+				)
5502
+			);
5503
+		}
5504
+		return apply_filters($tagName, null, $this, $args);
5505
+	}
5506
+
5507
+
5508
+
5509
+	/**
5510
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5511
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5512
+	 *
5513
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5514
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5515
+	 *                                                       the object's class name
5516
+	 *                                                       or object's ID
5517
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5518
+	 *                                                       exists in the database. If it does not, we add it
5519
+	 * @throws EE_Error
5520
+	 * @return EE_Base_Class
5521
+	 */
5522
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5523
+	{
5524
+		$className = $this->_get_class_name();
5525
+		if ($base_class_obj_or_id instanceof $className) {
5526
+			$model_object = $base_class_obj_or_id;
5527
+		} else {
5528
+			$primary_key_field = $this->get_primary_key_field();
5529
+			if (
5530
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5531
+				&& (
5532
+					is_int($base_class_obj_or_id)
5533
+					|| is_string($base_class_obj_or_id)
5534
+				)
5535
+			) {
5536
+				// assume it's an ID.
5537
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5538
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5539
+			} else if (
5540
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5541
+				&& is_string($base_class_obj_or_id)
5542
+			) {
5543
+				// assume its a string representation of the object
5544
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5545
+			} else {
5546
+				throw new EE_Error(
5547
+					sprintf(
5548
+						__(
5549
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5550
+							'event_espresso'
5551
+						),
5552
+						$base_class_obj_or_id,
5553
+						$this->_get_class_name(),
5554
+						print_r($base_class_obj_or_id, true)
5555
+					)
5556
+				);
5557
+			}
5558
+		}
5559
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5560
+			$model_object->save();
5561
+		}
5562
+		return $model_object;
5563
+	}
5564
+
5565
+
5566
+
5567
+	/**
5568
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5569
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5570
+	 * returns it ID.
5571
+	 *
5572
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5573
+	 * @return int|string depending on the type of this model object's ID
5574
+	 * @throws EE_Error
5575
+	 */
5576
+	public function ensure_is_ID($base_class_obj_or_id)
5577
+	{
5578
+		$className = $this->_get_class_name();
5579
+		if ($base_class_obj_or_id instanceof $className) {
5580
+			/** @var $base_class_obj_or_id EE_Base_Class */
5581
+			$id = $base_class_obj_or_id->ID();
5582
+		} elseif (is_int($base_class_obj_or_id)) {
5583
+			//assume it's an ID
5584
+			$id = $base_class_obj_or_id;
5585
+		} elseif (is_string($base_class_obj_or_id)) {
5586
+			//assume its a string representation of the object
5587
+			$id = $base_class_obj_or_id;
5588
+		} else {
5589
+			throw new EE_Error(sprintf(__("'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5590
+				'event_espresso'), $base_class_obj_or_id, $this->_get_class_name(),
5591
+				print_r($base_class_obj_or_id, true)));
5592
+		}
5593
+		return $id;
5594
+	}
5595
+
5596
+
5597
+
5598
+	/**
5599
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5600
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5601
+	 * been sanitized and converted into the appropriate domain.
5602
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5603
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5604
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5605
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5606
+	 * $EVT = EEM_Event::instance(); $old_setting =
5607
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5608
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5609
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5610
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5611
+	 *
5612
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5613
+	 * @return void
5614
+	 */
5615
+	public function assume_values_already_prepared_by_model_object(
5616
+		$values_already_prepared = self::not_prepared_by_model_object
5617
+	) {
5618
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5619
+	}
5620
+
5621
+
5622
+
5623
+	/**
5624
+	 * Read comments for assume_values_already_prepared_by_model_object()
5625
+	 *
5626
+	 * @return int
5627
+	 */
5628
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5629
+	{
5630
+		return $this->_values_already_prepared_by_model_object;
5631
+	}
5632
+
5633
+
5634
+
5635
+	/**
5636
+	 * Gets all the indexes on this model
5637
+	 *
5638
+	 * @return EE_Index[]
5639
+	 */
5640
+	public function indexes()
5641
+	{
5642
+		return $this->_indexes;
5643
+	}
5644
+
5645
+
5646
+
5647
+	/**
5648
+	 * Gets all the Unique Indexes on this model
5649
+	 *
5650
+	 * @return EE_Unique_Index[]
5651
+	 */
5652
+	public function unique_indexes()
5653
+	{
5654
+		$unique_indexes = array();
5655
+		foreach ($this->_indexes as $name => $index) {
5656
+			if ($index instanceof EE_Unique_Index) {
5657
+				$unique_indexes [$name] = $index;
5658
+			}
5659
+		}
5660
+		return $unique_indexes;
5661
+	}
5662
+
5663
+
5664
+
5665
+	/**
5666
+	 * Gets all the fields which, when combined, make the primary key.
5667
+	 * This is usually just an array with 1 element (the primary key), but in cases
5668
+	 * where there is no primary key, it's a combination of fields as defined
5669
+	 * on a primary index
5670
+	 *
5671
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5672
+	 * @throws EE_Error
5673
+	 */
5674
+	public function get_combined_primary_key_fields()
5675
+	{
5676
+		foreach ($this->indexes() as $index) {
5677
+			if ($index instanceof EE_Primary_Key_Index) {
5678
+				return $index->fields();
5679
+			}
5680
+		}
5681
+		return array($this->primary_key_name() => $this->get_primary_key_field());
5682
+	}
5683
+
5684
+
5685
+
5686
+	/**
5687
+	 * Used to build a primary key string (when the model has no primary key),
5688
+	 * which can be used a unique string to identify this model object.
5689
+	 *
5690
+	 * @param array $cols_n_values keys are field names, values are their values
5691
+	 * @return string
5692
+	 * @throws EE_Error
5693
+	 */
5694
+	public function get_index_primary_key_string($cols_n_values)
5695
+	{
5696
+		$cols_n_values_for_primary_key_index = array_intersect_key($cols_n_values,
5697
+			$this->get_combined_primary_key_fields());
5698
+		return http_build_query($cols_n_values_for_primary_key_index);
5699
+	}
5700
+
5701
+
5702
+
5703
+	/**
5704
+	 * Gets the field values from the primary key string
5705
+	 *
5706
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5707
+	 * @param string $index_primary_key_string
5708
+	 * @return null|array
5709
+	 * @throws EE_Error
5710
+	 */
5711
+	public function parse_index_primary_key_string($index_primary_key_string)
5712
+	{
5713
+		$key_fields = $this->get_combined_primary_key_fields();
5714
+		//check all of them are in the $id
5715
+		$key_vals_in_combined_pk = array();
5716
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5717
+		foreach ($key_fields as $key_field_name => $field_obj) {
5718
+			if (! isset($key_vals_in_combined_pk[$key_field_name])) {
5719
+				return null;
5720
+			}
5721
+		}
5722
+		return $key_vals_in_combined_pk;
5723
+	}
5724
+
5725
+
5726
+
5727
+	/**
5728
+	 * verifies that an array of key-value pairs for model fields has a key
5729
+	 * for each field comprising the primary key index
5730
+	 *
5731
+	 * @param array $key_vals
5732
+	 * @return boolean
5733
+	 * @throws EE_Error
5734
+	 */
5735
+	public function has_all_combined_primary_key_fields($key_vals)
5736
+	{
5737
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5738
+		foreach ($keys_it_should_have as $key) {
5739
+			if (! isset($key_vals[$key])) {
5740
+				return false;
5741
+			}
5742
+		}
5743
+		return true;
5744
+	}
5745
+
5746
+
5747
+
5748
+	/**
5749
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5750
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
5751
+	 *
5752
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5753
+	 * @param array               $query_params                     like EEM_Base::get_all's query_params.
5754
+	 * @throws EE_Error
5755
+	 * @return \EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5756
+	 *                                                              indexed)
5757
+	 */
5758
+	public function get_all_copies($model_object_or_attributes_array, $query_params = array())
5759
+	{
5760
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5761
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
5762
+		} elseif (is_array($model_object_or_attributes_array)) {
5763
+			$attributes_array = $model_object_or_attributes_array;
5764
+		} else {
5765
+			throw new EE_Error(sprintf(__("get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5766
+				"event_espresso"), $model_object_or_attributes_array));
5767
+		}
5768
+		//even copies obviously won't have the same ID, so remove the primary key
5769
+		//from the WHERE conditions for finding copies (if there is a primary key, of course)
5770
+		if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
5771
+			unset($attributes_array[$this->primary_key_name()]);
5772
+		}
5773
+		if (isset($query_params[0])) {
5774
+			$query_params[0] = array_merge($attributes_array, $query_params);
5775
+		} else {
5776
+			$query_params[0] = $attributes_array;
5777
+		}
5778
+		return $this->get_all($query_params);
5779
+	}
5780
+
5781
+
5782
+
5783
+	/**
5784
+	 * Gets the first copy we find. See get_all_copies for more details
5785
+	 *
5786
+	 * @param       mixed EE_Base_Class | array        $model_object_or_attributes_array
5787
+	 * @param array $query_params
5788
+	 * @return EE_Base_Class
5789
+	 * @throws EE_Error
5790
+	 */
5791
+	public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5792
+	{
5793
+		if (! is_array($query_params)) {
5794
+			EE_Error::doing_it_wrong('EEM_Base::get_one_copy',
5795
+				sprintf(__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5796
+					gettype($query_params)), '4.6.0');
5797
+			$query_params = array();
5798
+		}
5799
+		$query_params['limit'] = 1;
5800
+		$copies = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5801
+		if (is_array($copies)) {
5802
+			return array_shift($copies);
5803
+		}
5804
+		return null;
5805
+	}
5806
+
5807
+
5808
+
5809
+	/**
5810
+	 * Updates the item with the specified id. Ignores default query parameters because
5811
+	 * we have specified the ID, and its assumed we KNOW what we're doing
5812
+	 *
5813
+	 * @param array      $fields_n_values keys are field names, values are their new values
5814
+	 * @param int|string $id              the value of the primary key to update
5815
+	 * @return int number of rows updated
5816
+	 * @throws EE_Error
5817
+	 */
5818
+	public function update_by_ID($fields_n_values, $id)
5819
+	{
5820
+		$query_params = array(
5821
+			0                          => array($this->get_primary_key_field()->get_name() => $id),
5822
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
5823
+		);
5824
+		return $this->update($fields_n_values, $query_params);
5825
+	}
5826
+
5827
+
5828
+
5829
+	/**
5830
+	 * Changes an operator which was supplied to the models into one usable in SQL
5831
+	 *
5832
+	 * @param string $operator_supplied
5833
+	 * @return string an operator which can be used in SQL
5834
+	 * @throws EE_Error
5835
+	 */
5836
+	private function _prepare_operator_for_sql($operator_supplied)
5837
+	{
5838
+		$sql_operator = isset($this->_valid_operators[$operator_supplied]) ? $this->_valid_operators[$operator_supplied]
5839
+			: null;
5840
+		if ($sql_operator) {
5841
+			return $sql_operator;
5842
+		}
5843
+		throw new EE_Error(
5844
+			sprintf(
5845
+				__(
5846
+					"The operator '%s' is not in the list of valid operators: %s",
5847
+					"event_espresso"
5848
+				), $operator_supplied, implode(",", array_keys($this->_valid_operators))
5849
+			)
5850
+		);
5851
+	}
5852
+
5853
+
5854
+
5855
+	/**
5856
+	 * Gets the valid operators
5857
+	 * @return array keys are accepted strings, values are the SQL they are converted to
5858
+	 */
5859
+	public function valid_operators(){
5860
+		return $this->_valid_operators;
5861
+	}
5862
+
5863
+
5864
+
5865
+	/**
5866
+	 * Gets the between-style operators (take 2 arguments).
5867
+	 * @return array keys are accepted strings, values are the SQL they are converted to
5868
+	 */
5869
+	public function valid_between_style_operators()
5870
+	{
5871
+		return array_intersect(
5872
+			$this->valid_operators(),
5873
+			$this->_between_style_operators
5874
+		);
5875
+	}
5876
+
5877
+	/**
5878
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
5879
+	 * @return array keys are accepted strings, values are the SQL they are converted to
5880
+	 */
5881
+	public function valid_like_style_operators()
5882
+	{
5883
+		return array_intersect(
5884
+			$this->valid_operators(),
5885
+			$this->_like_style_operators
5886
+		);
5887
+	}
5888
+
5889
+	/**
5890
+	 * Gets the "in"-style operators
5891
+	 * @return array keys are accepted strings, values are the SQL they are converted to
5892
+	 */
5893
+	public function valid_in_style_operators()
5894
+	{
5895
+		return array_intersect(
5896
+			$this->valid_operators(),
5897
+			$this->_in_style_operators
5898
+		);
5899
+	}
5900
+
5901
+	/**
5902
+	 * Gets the "null"-style operators (accept no arguments)
5903
+	 * @return array keys are accepted strings, values are the SQL they are converted to
5904
+	 */
5905
+	public function valid_null_style_operators()
5906
+	{
5907
+		return array_intersect(
5908
+			$this->valid_operators(),
5909
+			$this->_null_style_operators
5910
+		);
5911
+	}
5912
+
5913
+	/**
5914
+	 * Gets an array where keys are the primary keys and values are their 'names'
5915
+	 * (as determined by the model object's name() function, which is often overridden)
5916
+	 *
5917
+	 * @param array $query_params like get_all's
5918
+	 * @return string[]
5919
+	 * @throws EE_Error
5920
+	 */
5921
+	public function get_all_names($query_params = array())
5922
+	{
5923
+		$objs = $this->get_all($query_params);
5924
+		$names = array();
5925
+		foreach ($objs as $obj) {
5926
+			$names[$obj->ID()] = $obj->name();
5927
+		}
5928
+		return $names;
5929
+	}
5930
+
5931
+
5932
+
5933
+	/**
5934
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
5935
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
5936
+	 * this is duplicated effort and reduces efficiency) you would be better to use
5937
+	 * array_keys() on $model_objects.
5938
+	 *
5939
+	 * @param \EE_Base_Class[] $model_objects
5940
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
5941
+	 *                                               in the returned array
5942
+	 * @return array
5943
+	 * @throws EE_Error
5944
+	 */
5945
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
5946
+	{
5947
+		if (! $this->has_primary_key_field()) {
5948
+			if (WP_DEBUG) {
5949
+				EE_Error::add_error(
5950
+					__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
5951
+					__FILE__,
5952
+					__FUNCTION__,
5953
+					__LINE__
5954
+				);
5955
+			}
5956
+		}
5957
+		$IDs = array();
5958
+		foreach ($model_objects as $model_object) {
5959
+			$id = $model_object->ID();
5960
+			if (! $id) {
5961
+				if ($filter_out_empty_ids) {
5962
+					continue;
5963
+				}
5964
+				if (WP_DEBUG) {
5965
+					EE_Error::add_error(
5966
+						__(
5967
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
5968
+							'event_espresso'
5969
+						),
5970
+						__FILE__,
5971
+						__FUNCTION__,
5972
+						__LINE__
5973
+					);
5974
+				}
5975
+			}
5976
+			$IDs[] = $id;
5977
+		}
5978
+		return $IDs;
5979
+	}
5980
+
5981
+
5982
+
5983
+	/**
5984
+	 * Returns the string used in capabilities relating to this model. If there
5985
+	 * are no capabilities that relate to this model returns false
5986
+	 *
5987
+	 * @return string|false
5988
+	 */
5989
+	public function cap_slug()
5990
+	{
5991
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
5992
+	}
5993
+
5994
+
5995
+
5996
+	/**
5997
+	 * Returns the capability-restrictions array (@see EEM_Base::_cap_restrictions).
5998
+	 * If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
5999
+	 * only returns the cap restrictions array in that context (ie, the array
6000
+	 * at that key)
6001
+	 *
6002
+	 * @param string $context
6003
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6004
+	 * @throws EE_Error
6005
+	 */
6006
+	public function cap_restrictions($context = EEM_Base::caps_read)
6007
+	{
6008
+		EEM_Base::verify_is_valid_cap_context($context);
6009
+		//check if we ought to run the restriction generator first
6010
+		if (
6011
+			isset($this->_cap_restriction_generators[$context])
6012
+			&& $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6013
+			&& ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6014
+		) {
6015
+			$this->_cap_restrictions[$context] = array_merge(
6016
+				$this->_cap_restrictions[$context],
6017
+				$this->_cap_restriction_generators[$context]->generate_restrictions()
6018
+			);
6019
+		}
6020
+		//and make sure we've finalized the construction of each restriction
6021
+		foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6022
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6023
+				$where_conditions_obj->_finalize_construct($this);
6024
+			}
6025
+		}
6026
+		return $this->_cap_restrictions[$context];
6027
+	}
6028
+
6029
+
6030
+
6031
+	/**
6032
+	 * Indicating whether or not this model thinks its a wp core model
6033
+	 *
6034
+	 * @return boolean
6035
+	 */
6036
+	public function is_wp_core_model()
6037
+	{
6038
+		return $this->_wp_core_model;
6039
+	}
6040
+
6041
+
6042
+
6043
+	/**
6044
+	 * Gets all the caps that are missing which impose a restriction on
6045
+	 * queries made in this context
6046
+	 *
6047
+	 * @param string $context one of EEM_Base::caps_ constants
6048
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6049
+	 * @throws EE_Error
6050
+	 */
6051
+	public function caps_missing($context = EEM_Base::caps_read)
6052
+	{
6053
+		$missing_caps = array();
6054
+		$cap_restrictions = $this->cap_restrictions($context);
6055
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6056
+			if (! EE_Capabilities::instance()
6057
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6058
+			) {
6059
+				$missing_caps[$cap] = $restriction_if_no_cap;
6060
+			}
6061
+		}
6062
+		return $missing_caps;
6063
+	}
6064
+
6065
+
6066
+
6067
+	/**
6068
+	 * Gets the mapping from capability contexts to action strings used in capability names
6069
+	 *
6070
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6071
+	 * one of 'read', 'edit', or 'delete'
6072
+	 */
6073
+	public function cap_contexts_to_cap_action_map()
6074
+	{
6075
+		return apply_filters('FHEE__EEM_Base__cap_contexts_to_cap_action_map', $this->_cap_contexts_to_cap_action_map,
6076
+			$this);
6077
+	}
6078
+
6079
+
6080
+
6081
+	/**
6082
+	 * Gets the action string for the specified capability context
6083
+	 *
6084
+	 * @param string $context
6085
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6086
+	 * @throws EE_Error
6087
+	 */
6088
+	public function cap_action_for_context($context)
6089
+	{
6090
+		$mapping = $this->cap_contexts_to_cap_action_map();
6091
+		if (isset($mapping[$context])) {
6092
+			return $mapping[$context];
6093
+		}
6094
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6095
+			return $action;
6096
+		}
6097
+		throw new EE_Error(
6098
+			sprintf(
6099
+				__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6100
+				$context,
6101
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6102
+			)
6103
+		);
6104
+	}
6105
+
6106
+
6107
+
6108
+	/**
6109
+	 * Returns all the capability contexts which are valid when querying models
6110
+	 *
6111
+	 * @return array
6112
+	 */
6113
+	public static function valid_cap_contexts()
6114
+	{
6115
+		return apply_filters('FHEE__EEM_Base__valid_cap_contexts', array(
6116
+			self::caps_read,
6117
+			self::caps_read_admin,
6118
+			self::caps_edit,
6119
+			self::caps_delete,
6120
+		));
6121
+	}
6122
+
6123
+
6124
+
6125
+	/**
6126
+	 * Returns all valid options for 'default_where_conditions'
6127
+	 *
6128
+	 * @return array
6129
+	 */
6130
+	public static function valid_default_where_conditions()
6131
+	{
6132
+		return array(
6133
+			EEM_Base::default_where_conditions_all,
6134
+			EEM_Base::default_where_conditions_this_only,
6135
+			EEM_Base::default_where_conditions_others_only,
6136
+			EEM_Base::default_where_conditions_minimum_all,
6137
+			EEM_Base::default_where_conditions_minimum_others,
6138
+			EEM_Base::default_where_conditions_none
6139
+		);
6140
+	}
6141
+
6142
+	// public static function default_where_conditions_full
6143
+	/**
6144
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6145
+	 *
6146
+	 * @param string $context
6147
+	 * @return bool
6148
+	 * @throws EE_Error
6149
+	 */
6150
+	static public function verify_is_valid_cap_context($context)
6151
+	{
6152
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6153
+		if (in_array($context, $valid_cap_contexts)) {
6154
+			return true;
6155
+		}
6156
+		throw new EE_Error(
6157
+			sprintf(
6158
+				__(
6159
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6160
+					'event_espresso'
6161
+				),
6162
+				$context,
6163
+				'EEM_Base',
6164
+				implode(',', $valid_cap_contexts)
6165
+			)
6166
+		);
6167
+	}
6168
+
6169
+
6170
+
6171
+	/**
6172
+	 * Clears all the models field caches. This is only useful when a sub-class
6173
+	 * might have added a field or something and these caches might be invalidated
6174
+	 */
6175
+	protected function _invalidate_field_caches()
6176
+	{
6177
+		$this->_cache_foreign_key_to_fields = array();
6178
+		$this->_cached_fields = null;
6179
+		$this->_cached_fields_non_db_only = null;
6180
+	}
6181
+
6182
+
6183
+
6184
+	/**
6185
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6186
+	 * (eg "and", "or", "not").
6187
+	 *
6188
+	 * @return array
6189
+	 */
6190
+	public function logic_query_param_keys()
6191
+	{
6192
+		return $this->_logic_query_param_keys;
6193
+	}
6194
+
6195
+
6196
+
6197
+	/**
6198
+	 * Determines whether or not the where query param array key is for a logic query param.
6199
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6200
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6201
+	 *
6202
+	 * @param $query_param_key
6203
+	 * @return bool
6204
+	 */
6205
+	public function is_logic_query_param_key($query_param_key)
6206
+	{
6207
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6208
+			if ($query_param_key === $logic_query_param_key
6209
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6210
+			) {
6211
+				return true;
6212
+			}
6213
+		}
6214
+		return false;
6215
+	}
6216 6216
 
6217 6217
 
6218 6218
 
Please login to merge, or discard this patch.
core/services/request/middleware/RecommendedVersions.php 1 patch
Indentation   +176 added lines, -176 removed lines patch added patch discarded remove patch
@@ -23,187 +23,187 @@
 block discarded – undo
23 23
 class RecommendedVersions extends Middleware
24 24
 {
25 25
 
26
-    /**
27
-     * converts a Request to a Response
28
-     *
29
-     * @param RequestInterface $request
30
-     * @param ResponseInterface      $response
31
-     * @return ResponseInterface
32
-     * @throws InvalidDataTypeException
33
-     */
34
-    public function handleRequest(RequestInterface $request, ResponseInterface $response)
35
-    {
36
-        $this->request  = $request;
37
-        $this->response = $response;
38
-        // check required WP version
39
-        if (! $this->minimumWordPressVersionRequired()) {
40
-            $this->request->unSetRequestParam('activate', true);
41
-            add_action('admin_notices', array($this, 'minimum_wp_version_error'), 1);
42
-            $this->response->terminateRequest();
43
-            $this->response->deactivatePlugin();
44
-        }
45
-        // check recommended PHP version
46
-        if (! $this->minimumPhpVersionRecommended()) {
47
-            $this->displayMinimumRecommendedPhpVersionNotice();
48
-        }
49
-        //upcoming required version
50
-        if (! $this->upcomingRequiredPhpVersion()) {
51
-            $this->displayUpcomingRequiredVersion();
52
-        }
53
-        $this->response = $this->processRequestStack($this->request, $this->response);
54
-        return $this->response;
55
-    }
56
-
57
-
58
-    /**
59
-     * Helper method to assess installed wp version against given values.
60
-     * By default this compares the required minimum version of WP for EE against the installed version of WP
61
-     * Note, $wp_version is the first parameter sent into the PHP version_compare function (what is being checked
62
-     * against) so consider that when sending in your values.
63
-     *
64
-     * @param string $version_to_check
65
-     * @param string $operator
66
-     * @return bool
67
-     */
68
-    public static function compareWordPressVersion($version_to_check = EE_MIN_WP_VER_REQUIRED, $operator = '>=')
69
-    {
70
-        global $wp_version;
71
-        return version_compare(
72
-        // first account for wp_version being pre-release
73
-        // (like RC, beta etc) which are usually in the format like 4.7-RC3-39519
74
-            strpos($wp_version, '-') > 0
75
-                ? substr($wp_version, 0, strpos($wp_version, '-'))
76
-                : $wp_version,
77
-            $version_to_check,
78
-            $operator
79
-        );
80
-    }
81
-
82
-
83
-
84
-    /**
85
-     * @return boolean
86
-     */
87
-    private function minimumWordPressVersionRequired()
88
-    {
89
-        return RecommendedVersions::compareWordPressVersion();
90
-    }
91
-
92
-
93
-
94
-    /**
95
-     * @param string $min_version
96
-     * @return boolean
97
-     */
98
-    private function checkPhpVersion($min_version = EE_MIN_PHP_VER_RECOMMENDED)
99
-    {
100
-        return version_compare(PHP_VERSION, $min_version, '>=') ? true : false;
101
-    }
102
-
103
-
104
-
105
-    /**
106
-     * @return boolean
107
-     */
108
-    private function minimumPhpVersionRecommended()
109
-    {
110
-        return $this->checkPhpVersion();
111
-    }
112
-
113
-
114
-
115
-    /**
116
-     * @return void
117
-     */
118
-    public function minimumWpVersionError()
119
-    {
120
-        global $wp_version;
121
-        ?>
26
+	/**
27
+	 * converts a Request to a Response
28
+	 *
29
+	 * @param RequestInterface $request
30
+	 * @param ResponseInterface      $response
31
+	 * @return ResponseInterface
32
+	 * @throws InvalidDataTypeException
33
+	 */
34
+	public function handleRequest(RequestInterface $request, ResponseInterface $response)
35
+	{
36
+		$this->request  = $request;
37
+		$this->response = $response;
38
+		// check required WP version
39
+		if (! $this->minimumWordPressVersionRequired()) {
40
+			$this->request->unSetRequestParam('activate', true);
41
+			add_action('admin_notices', array($this, 'minimum_wp_version_error'), 1);
42
+			$this->response->terminateRequest();
43
+			$this->response->deactivatePlugin();
44
+		}
45
+		// check recommended PHP version
46
+		if (! $this->minimumPhpVersionRecommended()) {
47
+			$this->displayMinimumRecommendedPhpVersionNotice();
48
+		}
49
+		//upcoming required version
50
+		if (! $this->upcomingRequiredPhpVersion()) {
51
+			$this->displayUpcomingRequiredVersion();
52
+		}
53
+		$this->response = $this->processRequestStack($this->request, $this->response);
54
+		return $this->response;
55
+	}
56
+
57
+
58
+	/**
59
+	 * Helper method to assess installed wp version against given values.
60
+	 * By default this compares the required minimum version of WP for EE against the installed version of WP
61
+	 * Note, $wp_version is the first parameter sent into the PHP version_compare function (what is being checked
62
+	 * against) so consider that when sending in your values.
63
+	 *
64
+	 * @param string $version_to_check
65
+	 * @param string $operator
66
+	 * @return bool
67
+	 */
68
+	public static function compareWordPressVersion($version_to_check = EE_MIN_WP_VER_REQUIRED, $operator = '>=')
69
+	{
70
+		global $wp_version;
71
+		return version_compare(
72
+		// first account for wp_version being pre-release
73
+		// (like RC, beta etc) which are usually in the format like 4.7-RC3-39519
74
+			strpos($wp_version, '-') > 0
75
+				? substr($wp_version, 0, strpos($wp_version, '-'))
76
+				: $wp_version,
77
+			$version_to_check,
78
+			$operator
79
+		);
80
+	}
81
+
82
+
83
+
84
+	/**
85
+	 * @return boolean
86
+	 */
87
+	private function minimumWordPressVersionRequired()
88
+	{
89
+		return RecommendedVersions::compareWordPressVersion();
90
+	}
91
+
92
+
93
+
94
+	/**
95
+	 * @param string $min_version
96
+	 * @return boolean
97
+	 */
98
+	private function checkPhpVersion($min_version = EE_MIN_PHP_VER_RECOMMENDED)
99
+	{
100
+		return version_compare(PHP_VERSION, $min_version, '>=') ? true : false;
101
+	}
102
+
103
+
104
+
105
+	/**
106
+	 * @return boolean
107
+	 */
108
+	private function minimumPhpVersionRecommended()
109
+	{
110
+		return $this->checkPhpVersion();
111
+	}
112
+
113
+
114
+
115
+	/**
116
+	 * @return void
117
+	 */
118
+	public function minimumWpVersionError()
119
+	{
120
+		global $wp_version;
121
+		?>
122 122
         <div class="error">
123 123
             <p>
124 124
                 <?php
125
-                printf(
126
-                    __('We\'re sorry, but Event Espresso requires WordPress version %1$s or greater in order to operate. You are currently running version %2$s.%3$sFor information on how to update your version of WordPress, please go to %4$s.',
127
-                        'event_espresso'),
128
-                    EE_MIN_WP_VER_REQUIRED,
129
-                    $wp_version,
130
-                    '<br/>',
131
-                    '<a href="http://codex.wordpress.org/Updating_WordPress">http://codex.wordpress.org/Updating_WordPress</a>'
132
-                );
133
-                ?>
125
+				printf(
126
+					__('We\'re sorry, but Event Espresso requires WordPress version %1$s or greater in order to operate. You are currently running version %2$s.%3$sFor information on how to update your version of WordPress, please go to %4$s.',
127
+						'event_espresso'),
128
+					EE_MIN_WP_VER_REQUIRED,
129
+					$wp_version,
130
+					'<br/>',
131
+					'<a href="http://codex.wordpress.org/Updating_WordPress">http://codex.wordpress.org/Updating_WordPress</a>'
132
+				);
133
+				?>
134 134
             </p>
135 135
         </div>
136 136
         <?php
137
-    }
138
-
139
-
140
-
141
-    /**
142
-     *    _display_minimum_recommended_php_version_notice
143
-     *
144
-     * @access private
145
-     * @return void
146
-     * @throws InvalidDataTypeException
147
-     */
148
-    private function displayMinimumRecommendedPhpVersionNotice()
149
-    {
150
-        if ($this->request->isAdmin()) {
151
-            new PersistentAdminNotice(
152
-                'php_version_' . str_replace('.', '-', EE_MIN_PHP_VER_RECOMMENDED) . '_recommended',
153
-                sprintf(
154
-                    esc_html__(
155
-                        'Event Espresso recommends PHP version %1$s or greater for optimal performance. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
156
-                        'event_espresso'
157
-                    ),
158
-                    EE_MIN_PHP_VER_RECOMMENDED,
159
-                    PHP_VERSION,
160
-                    '<br/>',
161
-                    '<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'
162
-                )
163
-            );
164
-        }
165
-    }
166
-
167
-
168
-    /**
169
-     * Returns whether the provided php version number is less than the current version of php installed on the server.
170
-     *
171
-     * @param string $version_required
172
-     * @return bool
173
-     */
174
-    private function upcomingRequiredPhpVersion($version_required = '5.5')
175
-    {
176
-        return true;
177
-        //return $this->checkPhpVersion($version_required);
178
-    }
179
-
180
-
181
-    /**
182
-     *  Sets a notice for an upcoming required version of PHP in the next update of EE core.
183
-     */
184
-    private function displayUpcomingRequiredVersion()
185
-    {
186
-        if ($this->request->isAdmin()
187
-            && apply_filters('FHEE__EE_Recommended_Versions__displayUpcomingRequiredVersion', true, $this->request)
188
-            && current_user_can('update_plugins')
189
-        ) {
190
-            add_action('admin_notices', function ()
191
-            {
192
-                echo '<div class="notice event-espresso-admin-notice notice-warning"><p>'
193
-                     . sprintf(
194
-                         esc_html__(
195
-                             'Please note: The next update of Event Espresso 4 will %1$srequire%2$s PHP 5.4.45 or greater.  Your web server\'s PHP version is %3$s.  You can contact your host and ask them to update your PHP version to at least PHP 5.6.  Please do not update to the new version of Event Espresso 4 until the PHP update is completed. Read about why keeping your server on the latest version of PHP is a good idea %4$shere%5$s',
196
-                             'event_espresso'
197
-                         ),
198
-                         '<strong>',
199
-                         '</strong>',
200
-                         PHP_VERSION,
201
-                         '<a href="https://wordpress.org/support/upgrade-php/">',
202
-                         '</a>'
203
-                     )
204
-                     . '</p></div>';
205
-            });
206
-        }
207
-    }
137
+	}
138
+
139
+
140
+
141
+	/**
142
+	 *    _display_minimum_recommended_php_version_notice
143
+	 *
144
+	 * @access private
145
+	 * @return void
146
+	 * @throws InvalidDataTypeException
147
+	 */
148
+	private function displayMinimumRecommendedPhpVersionNotice()
149
+	{
150
+		if ($this->request->isAdmin()) {
151
+			new PersistentAdminNotice(
152
+				'php_version_' . str_replace('.', '-', EE_MIN_PHP_VER_RECOMMENDED) . '_recommended',
153
+				sprintf(
154
+					esc_html__(
155
+						'Event Espresso recommends PHP version %1$s or greater for optimal performance. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
156
+						'event_espresso'
157
+					),
158
+					EE_MIN_PHP_VER_RECOMMENDED,
159
+					PHP_VERSION,
160
+					'<br/>',
161
+					'<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'
162
+				)
163
+			);
164
+		}
165
+	}
166
+
167
+
168
+	/**
169
+	 * Returns whether the provided php version number is less than the current version of php installed on the server.
170
+	 *
171
+	 * @param string $version_required
172
+	 * @return bool
173
+	 */
174
+	private function upcomingRequiredPhpVersion($version_required = '5.5')
175
+	{
176
+		return true;
177
+		//return $this->checkPhpVersion($version_required);
178
+	}
179
+
180
+
181
+	/**
182
+	 *  Sets a notice for an upcoming required version of PHP in the next update of EE core.
183
+	 */
184
+	private function displayUpcomingRequiredVersion()
185
+	{
186
+		if ($this->request->isAdmin()
187
+			&& apply_filters('FHEE__EE_Recommended_Versions__displayUpcomingRequiredVersion', true, $this->request)
188
+			&& current_user_can('update_plugins')
189
+		) {
190
+			add_action('admin_notices', function ()
191
+			{
192
+				echo '<div class="notice event-espresso-admin-notice notice-warning"><p>'
193
+					 . sprintf(
194
+						 esc_html__(
195
+							 'Please note: The next update of Event Espresso 4 will %1$srequire%2$s PHP 5.4.45 or greater.  Your web server\'s PHP version is %3$s.  You can contact your host and ask them to update your PHP version to at least PHP 5.6.  Please do not update to the new version of Event Espresso 4 until the PHP update is completed. Read about why keeping your server on the latest version of PHP is a good idea %4$shere%5$s',
196
+							 'event_espresso'
197
+						 ),
198
+						 '<strong>',
199
+						 '</strong>',
200
+						 PHP_VERSION,
201
+						 '<a href="https://wordpress.org/support/upgrade-php/">',
202
+						 '</a>'
203
+					 )
204
+					 . '</p></div>';
205
+			});
206
+		}
207
+	}
208 208
 }
209 209
 // Location: RecommendedVersions.php
Please login to merge, or discard this patch.
core/request_stack/EE_Request.core.php 1 patch
Indentation   +325 added lines, -325 removed lines patch added patch discarded remove patch
@@ -22,331 +22,331 @@
 block discarded – undo
22 22
 class EE_Request implements LegacyRequestInterface, InterminableInterface
23 23
 {
24 24
 
25
-    /**
26
-     * @var RequestInterface $request
27
-     */
28
-    private $request;
29
-
30
-    /**
31
-     * whether current request is for the admin but NOT via AJAX
32
-     *
33
-     * @var boolean $admin
34
-     */
35
-    public $admin = false;
36
-
37
-    /**
38
-     * whether current request is via AJAX
39
-     *
40
-     * @var boolean $ajax
41
-     */
42
-    public $ajax = false;
43
-
44
-    /**
45
-     * whether current request is via AJAX from the frontend of the site
46
-     *
47
-     * @var boolean $front_ajax
48
-     */
49
-    public $front_ajax = false;
50
-
51
-
52
-    /**
53
-     * @deprecated 4.9.53
54
-     * @param array $get
55
-     * @param array $post
56
-     * @param array $cookie
57
-     * @param array $server
58
-     */
59
-    public function __construct(
60
-        array $get = array(),
61
-        array $post = array(),
62
-        array $cookie = array(),
63
-        array $server = array()
64
-    ) {
65
-    }
66
-
67
-
68
-    /**
69
-     * @return RequestInterface
70
-     * @throws InvalidArgumentException
71
-     * @throws InvalidInterfaceException
72
-     * @throws InvalidDataTypeException
73
-     */
74
-    private function request()
75
-    {
76
-        if($this->request instanceof RequestInterface){
77
-            return $this->request;
78
-        }
79
-        $loader = LoaderFactory::getLoader();
80
-        $this->request = $loader->getShared('EventEspresso\core\services\request\RequestInterface');
81
-        return $this->request;
82
-    }
83
-
84
-
85
-    /**
86
-     * @param RequestInterface $request
87
-     */
88
-    public function setRequest(RequestInterface $request)
89
-    {
90
-        $this->request = $request;
91
-    }
92
-
93
-
94
-
95
-    /**
96
-     * @deprecated 4.9.53
97
-     * @return array
98
-     * @throws InvalidArgumentException
99
-     * @throws InvalidDataTypeException
100
-     * @throws InvalidInterfaceException
101
-     */
102
-    public function get_params()
103
-    {
104
-        return $this->request()->getParams();
105
-    }
106
-
107
-
108
-
109
-    /**
110
-     * @deprecated 4.9.53
111
-     * @return array
112
-     * @throws InvalidArgumentException
113
-     * @throws InvalidDataTypeException
114
-     * @throws InvalidInterfaceException
115
-     */
116
-    public function post_params()
117
-    {
118
-        return $this->request()->postParams();
119
-    }
120
-
121
-
122
-
123
-    /**
124
-     * @deprecated 4.9.53
125
-     * @return array
126
-     * @throws InvalidArgumentException
127
-     * @throws InvalidDataTypeException
128
-     * @throws InvalidInterfaceException
129
-     */
130
-    public function cookie_params()
131
-    {
132
-        return $this->request()->cookieParams();
133
-    }
134
-
135
-
136
-    /**
137
-     * @deprecated 4.9.53
138
-     * @return array
139
-     * @throws InvalidArgumentException
140
-     * @throws InvalidDataTypeException
141
-     * @throws InvalidInterfaceException
142
-     */
143
-    public function server_params()
144
-    {
145
-        return $this->request()->serverParams();
146
-    }
147
-
148
-
149
-
150
-    /**
151
-     * returns contents of $_REQUEST
152
-     *
153
-     * @deprecated 4.9.53
154
-     * @return array
155
-     * @throws InvalidArgumentException
156
-     * @throws InvalidDataTypeException
157
-     * @throws InvalidInterfaceException
158
-     */
159
-    public function params()
160
-    {
161
-        return $this->request()->requestParams();
162
-    }
163
-
164
-
165
-
166
-    /**
167
-     * @deprecated 4.9.53
168
-     * @param      $key
169
-     * @param      $value
170
-     * @param bool $override_ee
171
-     * @return void
172
-     * @throws InvalidArgumentException
173
-     * @throws InvalidDataTypeException
174
-     * @throws InvalidInterfaceException
175
-     */
176
-    public function set($key, $value, $override_ee = false)
177
-    {
178
-        $this->request()->setRequestParam($key, $value, $override_ee);
179
-    }
180
-
181
-
182
-
183
-    /**
184
-     * returns   the value for a request param if the given key exists
185
-     *
186
-     * @deprecated 4.9.53
187
-     * @param      $key
188
-     * @param null $default
189
-     * @return mixed
190
-     * @throws InvalidArgumentException
191
-     * @throws InvalidDataTypeException
192
-     * @throws InvalidInterfaceException
193
-     */
194
-    public function get($key, $default = null)
195
-    {
196
-        return $this->request()->getRequestParam($key, $default);
197
-    }
198
-
199
-
200
-
201
-    /**
202
-     * check if param exists
203
-     *
204
-     * @deprecated 4.9.53
205
-     * @param $key
206
-     * @return bool
207
-     * @throws InvalidArgumentException
208
-     * @throws InvalidDataTypeException
209
-     * @throws InvalidInterfaceException
210
-     */
211
-    public function is_set($key)
212
-    {
213
-        return $this->request()->requestParamIsSet($key);
214
-    }
215
-
216
-
217
-
218
-    /**
219
-     * remove param
220
-     *
221
-     * @deprecated 4.9.53
222
-     * @param      $key
223
-     * @param bool $unset_from_global_too
224
-     * @throws InvalidArgumentException
225
-     * @throws InvalidDataTypeException
226
-     * @throws InvalidInterfaceException
227
-     */
228
-    public function un_set($key, $unset_from_global_too = false)
229
-    {
230
-        $this->request()->unSetRequestParam($key, $unset_from_global_too);
231
-    }
232
-
233
-
234
-
235
-    /**
236
-     * @deprecated 4.9.53
237
-     * @return string
238
-     * @throws InvalidArgumentException
239
-     * @throws InvalidDataTypeException
240
-     * @throws InvalidInterfaceException
241
-     */
242
-    public function ip_address()
243
-    {
244
-        return $this->request()->ipAddress();
245
-    }
246
-
247
-
248
-    /**
249
-     * @deprecated 4.9.53
250
-     * @return bool
251
-     * @throws InvalidArgumentException
252
-     * @throws InvalidDataTypeException
253
-     * @throws InvalidInterfaceException
254
-     */
255
-    public function isAdmin()
256
-    {
257
-        return $this->request()->isAdmin();
258
-    }
259
-
260
-
261
-    /**
262
-     * @deprecated 4.9.53
263
-     * @return mixed
264
-     * @throws InvalidArgumentException
265
-     * @throws InvalidDataTypeException
266
-     * @throws InvalidInterfaceException
267
-     */
268
-    public function isAjax()
269
-    {
270
-        return $this->request()->isAjax();
271
-    }
272
-
273
-
274
-    /**
275
-     * @deprecated 4.9.53
276
-     * @return mixed
277
-     * @throws InvalidArgumentException
278
-     * @throws InvalidDataTypeException
279
-     * @throws InvalidInterfaceException
280
-     */
281
-    public function isFrontAjax()
282
-    {
283
-        return $this->request()->isFrontAjax();
284
-    }
285
-
286
-
287
-    /**
288
-     * @deprecated 4.9.53
289
-     * @return mixed|string
290
-     * @throws InvalidArgumentException
291
-     * @throws InvalidDataTypeException
292
-     * @throws InvalidInterfaceException
293
-     */
294
-    public function requestUri()
295
-    {
296
-        return $this->request()->requestUri();
297
-    }
298
-
299
-
300
-    /**
301
-     * @deprecated 4.9.53
302
-     * @return string
303
-     * @throws InvalidArgumentException
304
-     * @throws InvalidDataTypeException
305
-     * @throws InvalidInterfaceException
306
-     */
307
-    public function userAgent()
308
-    {
309
-        return $this->request()->userAgent();
310
-    }
311
-
312
-
313
-    /**
314
-     * @deprecated 4.9.53
315
-     * @param string $user_agent
316
-     * @throws InvalidArgumentException
317
-     * @throws InvalidDataTypeException
318
-     * @throws InvalidInterfaceException
319
-     */
320
-    public function setUserAgent($user_agent = '')
321
-    {
322
-        $this->request()->setUserAgent($user_agent);
323
-    }
324
-
325
-
326
-    /**
327
-     * @deprecated 4.9.53
328
-     * @return bool
329
-     * @throws InvalidArgumentException
330
-     * @throws InvalidDataTypeException
331
-     * @throws InvalidInterfaceException
332
-     */
333
-    public function isBot()
334
-    {
335
-        return $this->request()->isBot();
336
-    }
337
-
338
-
339
-    /**
340
-     * @deprecated 4.9.53
341
-     * @param bool $is_bot
342
-     * @throws InvalidArgumentException
343
-     * @throws InvalidDataTypeException
344
-     * @throws InvalidInterfaceException
345
-     */
346
-    public function setIsBot($is_bot)
347
-    {
348
-        $this->request()->setIsBot($is_bot);
349
-    }
25
+	/**
26
+	 * @var RequestInterface $request
27
+	 */
28
+	private $request;
29
+
30
+	/**
31
+	 * whether current request is for the admin but NOT via AJAX
32
+	 *
33
+	 * @var boolean $admin
34
+	 */
35
+	public $admin = false;
36
+
37
+	/**
38
+	 * whether current request is via AJAX
39
+	 *
40
+	 * @var boolean $ajax
41
+	 */
42
+	public $ajax = false;
43
+
44
+	/**
45
+	 * whether current request is via AJAX from the frontend of the site
46
+	 *
47
+	 * @var boolean $front_ajax
48
+	 */
49
+	public $front_ajax = false;
50
+
51
+
52
+	/**
53
+	 * @deprecated 4.9.53
54
+	 * @param array $get
55
+	 * @param array $post
56
+	 * @param array $cookie
57
+	 * @param array $server
58
+	 */
59
+	public function __construct(
60
+		array $get = array(),
61
+		array $post = array(),
62
+		array $cookie = array(),
63
+		array $server = array()
64
+	) {
65
+	}
66
+
67
+
68
+	/**
69
+	 * @return RequestInterface
70
+	 * @throws InvalidArgumentException
71
+	 * @throws InvalidInterfaceException
72
+	 * @throws InvalidDataTypeException
73
+	 */
74
+	private function request()
75
+	{
76
+		if($this->request instanceof RequestInterface){
77
+			return $this->request;
78
+		}
79
+		$loader = LoaderFactory::getLoader();
80
+		$this->request = $loader->getShared('EventEspresso\core\services\request\RequestInterface');
81
+		return $this->request;
82
+	}
83
+
84
+
85
+	/**
86
+	 * @param RequestInterface $request
87
+	 */
88
+	public function setRequest(RequestInterface $request)
89
+	{
90
+		$this->request = $request;
91
+	}
92
+
93
+
94
+
95
+	/**
96
+	 * @deprecated 4.9.53
97
+	 * @return array
98
+	 * @throws InvalidArgumentException
99
+	 * @throws InvalidDataTypeException
100
+	 * @throws InvalidInterfaceException
101
+	 */
102
+	public function get_params()
103
+	{
104
+		return $this->request()->getParams();
105
+	}
106
+
107
+
108
+
109
+	/**
110
+	 * @deprecated 4.9.53
111
+	 * @return array
112
+	 * @throws InvalidArgumentException
113
+	 * @throws InvalidDataTypeException
114
+	 * @throws InvalidInterfaceException
115
+	 */
116
+	public function post_params()
117
+	{
118
+		return $this->request()->postParams();
119
+	}
120
+
121
+
122
+
123
+	/**
124
+	 * @deprecated 4.9.53
125
+	 * @return array
126
+	 * @throws InvalidArgumentException
127
+	 * @throws InvalidDataTypeException
128
+	 * @throws InvalidInterfaceException
129
+	 */
130
+	public function cookie_params()
131
+	{
132
+		return $this->request()->cookieParams();
133
+	}
134
+
135
+
136
+	/**
137
+	 * @deprecated 4.9.53
138
+	 * @return array
139
+	 * @throws InvalidArgumentException
140
+	 * @throws InvalidDataTypeException
141
+	 * @throws InvalidInterfaceException
142
+	 */
143
+	public function server_params()
144
+	{
145
+		return $this->request()->serverParams();
146
+	}
147
+
148
+
149
+
150
+	/**
151
+	 * returns contents of $_REQUEST
152
+	 *
153
+	 * @deprecated 4.9.53
154
+	 * @return array
155
+	 * @throws InvalidArgumentException
156
+	 * @throws InvalidDataTypeException
157
+	 * @throws InvalidInterfaceException
158
+	 */
159
+	public function params()
160
+	{
161
+		return $this->request()->requestParams();
162
+	}
163
+
164
+
165
+
166
+	/**
167
+	 * @deprecated 4.9.53
168
+	 * @param      $key
169
+	 * @param      $value
170
+	 * @param bool $override_ee
171
+	 * @return void
172
+	 * @throws InvalidArgumentException
173
+	 * @throws InvalidDataTypeException
174
+	 * @throws InvalidInterfaceException
175
+	 */
176
+	public function set($key, $value, $override_ee = false)
177
+	{
178
+		$this->request()->setRequestParam($key, $value, $override_ee);
179
+	}
180
+
181
+
182
+
183
+	/**
184
+	 * returns   the value for a request param if the given key exists
185
+	 *
186
+	 * @deprecated 4.9.53
187
+	 * @param      $key
188
+	 * @param null $default
189
+	 * @return mixed
190
+	 * @throws InvalidArgumentException
191
+	 * @throws InvalidDataTypeException
192
+	 * @throws InvalidInterfaceException
193
+	 */
194
+	public function get($key, $default = null)
195
+	{
196
+		return $this->request()->getRequestParam($key, $default);
197
+	}
198
+
199
+
200
+
201
+	/**
202
+	 * check if param exists
203
+	 *
204
+	 * @deprecated 4.9.53
205
+	 * @param $key
206
+	 * @return bool
207
+	 * @throws InvalidArgumentException
208
+	 * @throws InvalidDataTypeException
209
+	 * @throws InvalidInterfaceException
210
+	 */
211
+	public function is_set($key)
212
+	{
213
+		return $this->request()->requestParamIsSet($key);
214
+	}
215
+
216
+
217
+
218
+	/**
219
+	 * remove param
220
+	 *
221
+	 * @deprecated 4.9.53
222
+	 * @param      $key
223
+	 * @param bool $unset_from_global_too
224
+	 * @throws InvalidArgumentException
225
+	 * @throws InvalidDataTypeException
226
+	 * @throws InvalidInterfaceException
227
+	 */
228
+	public function un_set($key, $unset_from_global_too = false)
229
+	{
230
+		$this->request()->unSetRequestParam($key, $unset_from_global_too);
231
+	}
232
+
233
+
234
+
235
+	/**
236
+	 * @deprecated 4.9.53
237
+	 * @return string
238
+	 * @throws InvalidArgumentException
239
+	 * @throws InvalidDataTypeException
240
+	 * @throws InvalidInterfaceException
241
+	 */
242
+	public function ip_address()
243
+	{
244
+		return $this->request()->ipAddress();
245
+	}
246
+
247
+
248
+	/**
249
+	 * @deprecated 4.9.53
250
+	 * @return bool
251
+	 * @throws InvalidArgumentException
252
+	 * @throws InvalidDataTypeException
253
+	 * @throws InvalidInterfaceException
254
+	 */
255
+	public function isAdmin()
256
+	{
257
+		return $this->request()->isAdmin();
258
+	}
259
+
260
+
261
+	/**
262
+	 * @deprecated 4.9.53
263
+	 * @return mixed
264
+	 * @throws InvalidArgumentException
265
+	 * @throws InvalidDataTypeException
266
+	 * @throws InvalidInterfaceException
267
+	 */
268
+	public function isAjax()
269
+	{
270
+		return $this->request()->isAjax();
271
+	}
272
+
273
+
274
+	/**
275
+	 * @deprecated 4.9.53
276
+	 * @return mixed
277
+	 * @throws InvalidArgumentException
278
+	 * @throws InvalidDataTypeException
279
+	 * @throws InvalidInterfaceException
280
+	 */
281
+	public function isFrontAjax()
282
+	{
283
+		return $this->request()->isFrontAjax();
284
+	}
285
+
286
+
287
+	/**
288
+	 * @deprecated 4.9.53
289
+	 * @return mixed|string
290
+	 * @throws InvalidArgumentException
291
+	 * @throws InvalidDataTypeException
292
+	 * @throws InvalidInterfaceException
293
+	 */
294
+	public function requestUri()
295
+	{
296
+		return $this->request()->requestUri();
297
+	}
298
+
299
+
300
+	/**
301
+	 * @deprecated 4.9.53
302
+	 * @return string
303
+	 * @throws InvalidArgumentException
304
+	 * @throws InvalidDataTypeException
305
+	 * @throws InvalidInterfaceException
306
+	 */
307
+	public function userAgent()
308
+	{
309
+		return $this->request()->userAgent();
310
+	}
311
+
312
+
313
+	/**
314
+	 * @deprecated 4.9.53
315
+	 * @param string $user_agent
316
+	 * @throws InvalidArgumentException
317
+	 * @throws InvalidDataTypeException
318
+	 * @throws InvalidInterfaceException
319
+	 */
320
+	public function setUserAgent($user_agent = '')
321
+	{
322
+		$this->request()->setUserAgent($user_agent);
323
+	}
324
+
325
+
326
+	/**
327
+	 * @deprecated 4.9.53
328
+	 * @return bool
329
+	 * @throws InvalidArgumentException
330
+	 * @throws InvalidDataTypeException
331
+	 * @throws InvalidInterfaceException
332
+	 */
333
+	public function isBot()
334
+	{
335
+		return $this->request()->isBot();
336
+	}
337
+
338
+
339
+	/**
340
+	 * @deprecated 4.9.53
341
+	 * @param bool $is_bot
342
+	 * @throws InvalidArgumentException
343
+	 * @throws InvalidDataTypeException
344
+	 * @throws InvalidInterfaceException
345
+	 */
346
+	public function setIsBot($is_bot)
347
+	{
348
+		$this->request()->setIsBot($is_bot);
349
+	}
350 350
 
351 351
 
352 352
 
Please login to merge, or discard this patch.
espresso.php 2 patches
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -38,7 +38,7 @@  discard block
 block discarded – undo
38 38
  * @since       4.0
39 39
  */
40 40
 if (function_exists('espresso_version')) {
41
-    if (! function_exists('espresso_duplicate_plugin_error')) {
41
+    if ( ! function_exists('espresso_duplicate_plugin_error')) {
42 42
         /**
43 43
          *    espresso_duplicate_plugin_error
44 44
          *    displays if more than one version of EE is activated at the same time
@@ -63,7 +63,7 @@  discard block
 block discarded – undo
63 63
 
64 64
 } else {
65 65
     define('EE_MIN_PHP_VER_REQUIRED', '5.4.0');
66
-    if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
66
+    if ( ! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
67 67
         /**
68 68
          * espresso_minimum_php_version_error
69 69
          * @return void
@@ -116,11 +116,11 @@  discard block
 block discarded – undo
116 116
 
117 117
         register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
118 118
 
119
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
119
+        require_once __DIR__.'/core/bootstrap_espresso.php';
120 120
         bootstrap_espresso();
121 121
     }
122 122
 }
123
-if (! function_exists('espresso_deactivate_plugin')) {
123
+if ( ! function_exists('espresso_deactivate_plugin')) {
124 124
     /**
125 125
      *    deactivate_plugin
126 126
      * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
@@ -131,8 +131,8 @@  discard block
 block discarded – undo
131 131
      */
132 132
     function espresso_deactivate_plugin($plugin_basename = '')
133 133
     {
134
-        if (! function_exists('deactivate_plugins')) {
135
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
134
+        if ( ! function_exists('deactivate_plugins')) {
135
+            require_once ABSPATH.'wp-admin/includes/plugin.php';
136 136
         }
137 137
         unset($_GET['activate'], $_REQUEST['activate']);
138 138
         deactivate_plugins($plugin_basename);
Please login to merge, or discard this patch.
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -38,103 +38,103 @@
 block discarded – undo
38 38
  * @since       4.0
39 39
  */
40 40
 if (function_exists('espresso_version')) {
41
-    if (! function_exists('espresso_duplicate_plugin_error')) {
42
-        /**
43
-         *    espresso_duplicate_plugin_error
44
-         *    displays if more than one version of EE is activated at the same time
45
-         */
46
-        function espresso_duplicate_plugin_error()
47
-        {
48
-            ?>
41
+	if (! function_exists('espresso_duplicate_plugin_error')) {
42
+		/**
43
+		 *    espresso_duplicate_plugin_error
44
+		 *    displays if more than one version of EE is activated at the same time
45
+		 */
46
+		function espresso_duplicate_plugin_error()
47
+		{
48
+			?>
49 49
             <div class="error">
50 50
                 <p>
51 51
                     <?php
52
-                    echo esc_html__(
53
-                        'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
54
-                        'event_espresso'
55
-                    ); ?>
52
+					echo esc_html__(
53
+						'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
54
+						'event_espresso'
55
+					); ?>
56 56
                 </p>
57 57
             </div>
58 58
             <?php
59
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
60
-        }
61
-    }
62
-    add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
59
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
60
+		}
61
+	}
62
+	add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
63 63
 
64 64
 } else {
65
-    define('EE_MIN_PHP_VER_REQUIRED', '5.4.0');
66
-    if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
67
-        /**
68
-         * espresso_minimum_php_version_error
69
-         * @return void
70
-         */
71
-        function espresso_minimum_php_version_error()
72
-        {
73
-            ?>
65
+	define('EE_MIN_PHP_VER_REQUIRED', '5.4.0');
66
+	if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
67
+		/**
68
+		 * espresso_minimum_php_version_error
69
+		 * @return void
70
+		 */
71
+		function espresso_minimum_php_version_error()
72
+		{
73
+			?>
74 74
             <div class="error">
75 75
                 <p>
76 76
                     <?php
77
-                    printf(
78
-                        esc_html__(
79
-                            'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
80
-                            'event_espresso'
81
-                        ),
82
-                        EE_MIN_PHP_VER_REQUIRED,
83
-                        PHP_VERSION,
84
-                        '<br/>',
85
-                        '<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'
86
-                    );
87
-                    ?>
77
+					printf(
78
+						esc_html__(
79
+							'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
80
+							'event_espresso'
81
+						),
82
+						EE_MIN_PHP_VER_REQUIRED,
83
+						PHP_VERSION,
84
+						'<br/>',
85
+						'<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'
86
+					);
87
+					?>
88 88
                 </p>
89 89
             </div>
90 90
             <?php
91
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
92
-        }
91
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
92
+		}
93 93
 
94
-        add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
95
-    } else {
96
-        define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
97
-        /**
98
-         * espresso_version
99
-         * Returns the plugin version
100
-         *
101
-         * @return string
102
-         */
103
-        function espresso_version()
104
-        {
105
-            return apply_filters('FHEE__espresso__espresso_version', '4.9.59.rc.049');
106
-        }
94
+		add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
95
+	} else {
96
+		define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
97
+		/**
98
+		 * espresso_version
99
+		 * Returns the plugin version
100
+		 *
101
+		 * @return string
102
+		 */
103
+		function espresso_version()
104
+		{
105
+			return apply_filters('FHEE__espresso__espresso_version', '4.9.59.rc.049');
106
+		}
107 107
 
108
-        /**
109
-         * espresso_plugin_activation
110
-         * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
111
-         */
112
-        function espresso_plugin_activation()
113
-        {
114
-            update_option('ee_espresso_activation', true);
115
-        }
108
+		/**
109
+		 * espresso_plugin_activation
110
+		 * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
111
+		 */
112
+		function espresso_plugin_activation()
113
+		{
114
+			update_option('ee_espresso_activation', true);
115
+		}
116 116
 
117
-        register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
117
+		register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
118 118
 
119
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
120
-        bootstrap_espresso();
121
-    }
119
+		require_once __DIR__ . '/core/bootstrap_espresso.php';
120
+		bootstrap_espresso();
121
+	}
122 122
 }
123 123
 if (! function_exists('espresso_deactivate_plugin')) {
124
-    /**
125
-     *    deactivate_plugin
126
-     * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
127
-     *
128
-     * @access public
129
-     * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
130
-     * @return    void
131
-     */
132
-    function espresso_deactivate_plugin($plugin_basename = '')
133
-    {
134
-        if (! function_exists('deactivate_plugins')) {
135
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
136
-        }
137
-        unset($_GET['activate'], $_REQUEST['activate']);
138
-        deactivate_plugins($plugin_basename);
139
-    }
124
+	/**
125
+	 *    deactivate_plugin
126
+	 * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
127
+	 *
128
+	 * @access public
129
+	 * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
130
+	 * @return    void
131
+	 */
132
+	function espresso_deactivate_plugin($plugin_basename = '')
133
+	{
134
+		if (! function_exists('deactivate_plugins')) {
135
+			require_once ABSPATH . 'wp-admin/includes/plugin.php';
136
+		}
137
+		unset($_GET['activate'], $_REQUEST['activate']);
138
+		deactivate_plugins($plugin_basename);
139
+	}
140 140
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Payment.model.php 1 patch
Spacing   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -8,7 +8,7 @@  discard block
 block discarded – undo
8 8
  * @author				Michael Nelson, Brent Christensen
9 9
  *
10 10
  */
11
-class EEM_Payment extends EEM_Base implements EEMI_Payment{
11
+class EEM_Payment extends EEM_Base implements EEMI_Payment {
12 12
 
13 13
   	// private instance of the Payment object
14 14
 	protected static $_instance = NULL;
@@ -56,28 +56,28 @@  discard block
 block discarded – undo
56 56
 	 *		@param string $timezone string representing the timezone we want to set for returned Date Time Strings (and any incoming timezone data that gets saved).  Note this just sends the timezone info to the date time model field objects.  Default is NULL (and will be assumed using the set timezone in the 'timezone_string' wp option)
57 57
 	 *		@return EEM_Payment
58 58
 	 */
59
-	protected function __construct( $timezone ) {
59
+	protected function __construct($timezone) {
60 60
 
61
-		$this->singular_item = __('Payment','event_espresso');
62
-		$this->plural_item = __('Payments','event_espresso');
61
+		$this->singular_item = __('Payment', 'event_espresso');
62
+		$this->plural_item = __('Payments', 'event_espresso');
63 63
 
64 64
 		$this->_tables = array(
65
-			'Payment'=>new EE_Primary_Table('esp_payment','PAY_ID')
65
+			'Payment'=>new EE_Primary_Table('esp_payment', 'PAY_ID')
66 66
 		);
67 67
 		$this->_fields = array(
68 68
 			'Payment'=>array(
69
-				'PAY_ID'=>new EE_Primary_Key_Int_Field('PAY_ID', __('Payment ID','event_espresso')),
70
-				'TXN_ID'=>new EE_Foreign_Key_Int_Field('TXN_ID', __('Transaction ID','event_espresso'), false, 0, 'Transaction'),
71
-				'STS_ID'=>new EE_Foreign_Key_String_Field('STS_ID', __('Status ID','event_espresso'), false, EEM_Payment::status_id_failed, 'Status'),
72
-				'PAY_timestamp'=> new EE_Datetime_Field('PAY_timestamp', __('Timestamp of when payment was attempted','event_espresso'), false, EE_Datetime_Field::now, $timezone ),
73
-				'PAY_source'=>new EE_All_Caps_Text_Field('PAY_source', __('User-friendly description of payment','event_espresso'), false, 'CART'),
74
-				'PAY_amount'=>new EE_Money_Field('PAY_amount', __('Amount Payment should be for','event_espresso'), false, 0),
69
+				'PAY_ID'=>new EE_Primary_Key_Int_Field('PAY_ID', __('Payment ID', 'event_espresso')),
70
+				'TXN_ID'=>new EE_Foreign_Key_Int_Field('TXN_ID', __('Transaction ID', 'event_espresso'), false, 0, 'Transaction'),
71
+				'STS_ID'=>new EE_Foreign_Key_String_Field('STS_ID', __('Status ID', 'event_espresso'), false, EEM_Payment::status_id_failed, 'Status'),
72
+				'PAY_timestamp'=> new EE_Datetime_Field('PAY_timestamp', __('Timestamp of when payment was attempted', 'event_espresso'), false, EE_Datetime_Field::now, $timezone),
73
+				'PAY_source'=>new EE_All_Caps_Text_Field('PAY_source', __('User-friendly description of payment', 'event_espresso'), false, 'CART'),
74
+				'PAY_amount'=>new EE_Money_Field('PAY_amount', __('Amount Payment should be for', 'event_espresso'), false, 0),
75 75
 				'PMD_ID'=>new EE_Foreign_Key_Int_Field('PMD_ID', __("Payment Method ID", 'event_espresso'), false, NULL, 'Payment_Method'),
76
-				'PAY_gateway_response'=>new EE_Plain_Text_Field('PAY_gateway_response', __('Response from Gateway about the payment','event_espresso'), false, ''),
77
-				'PAY_txn_id_chq_nmbr'=>new EE_Plain_Text_Field('PAY_txn_id_chq_nmbr', __('Gateway Transaction ID or Cheque Number','event_espresso'), true, ''),
78
-				'PAY_po_number'=>new EE_Plain_Text_Field('PAY_po_number', __('Purchase or Sales Number','event_espresso'), true, ''),
79
-				'PAY_extra_accntng'=>new EE_Simple_HTML_Field('PAY_extra_accntng', __('Extra Account Info','event_espresso'), true, ''),
80
-				'PAY_details'=>new EE_Serialized_Text_Field('PAY_details', __('Full Gateway response about payment','event_espresso'), true, ''),
76
+				'PAY_gateway_response'=>new EE_Plain_Text_Field('PAY_gateway_response', __('Response from Gateway about the payment', 'event_espresso'), false, ''),
77
+				'PAY_txn_id_chq_nmbr'=>new EE_Plain_Text_Field('PAY_txn_id_chq_nmbr', __('Gateway Transaction ID or Cheque Number', 'event_espresso'), true, ''),
78
+				'PAY_po_number'=>new EE_Plain_Text_Field('PAY_po_number', __('Purchase or Sales Number', 'event_espresso'), true, ''),
79
+				'PAY_extra_accntng'=>new EE_Simple_HTML_Field('PAY_extra_accntng', __('Extra Account Info', 'event_espresso'), true, ''),
80
+				'PAY_details'=>new EE_Serialized_Text_Field('PAY_details', __('Full Gateway response about payment', 'event_espresso'), true, ''),
81 81
 				'PAY_redirect_url'=>new EE_Plain_Text_Field('PAY_redirect_url', __("Redirect URL", 'event_espresso'), true),
82 82
 				'PAY_redirect_args'=>new EE_Serialized_Text_Field('PAY_redirect_args', __("Key-Value POST vars to send along with redirect", 'event_espresso'), true)
83 83
 			)
@@ -87,11 +87,11 @@  discard block
 block discarded – undo
87 87
 			'Status'=> new EE_Belongs_To_Relation(),
88 88
 			'Payment_Method'=>new EE_Belongs_To_Relation(),
89 89
 			'Registration_Payment' => new EE_Has_Many_Relation(),
90
-			'Registration' => new EE_HABTM_Relation( 'Registration_Payment' ),
90
+			'Registration' => new EE_HABTM_Relation('Registration_Payment'),
91 91
 		);
92 92
 		$this->_model_chain_to_wp_user = 'Payment_Method';
93 93
 		$this->_caps_slug = 'transactions';
94
-		parent::__construct( $timezone );
94
+		parent::__construct($timezone);
95 95
 	}
96 96
 
97 97
 
@@ -103,7 +103,7 @@  discard block
 block discarded – undo
103 103
 	 * @param string $PAY_txn_id_chq_nmbr
104 104
 	 * @return EE_Payment
105 105
 	 */
106
-	public function get_payment_by_txn_id_chq_nmbr( $PAY_txn_id_chq_nmbr ){
106
+	public function get_payment_by_txn_id_chq_nmbr($PAY_txn_id_chq_nmbr) {
107 107
 		return $this->get_one(array(array('PAY_txn_id_chq_nmbr'=>$PAY_txn_id_chq_nmbr)));
108 108
 	}
109 109
 
@@ -119,15 +119,15 @@  discard block
 block discarded – undo
119 119
 	 *		@param	string	$status_of_payment one of EEM_Payment::status_id_*, like 'PAP','PCN',etc. If none is provided, gets payments with any status
120 120
 	*		@return		EE_Payment[]
121 121
 	*/
122
-	public function get_payments_for_transaction( $TXN_ID = FALSE, $status_of_payment = null ) {
122
+	public function get_payments_for_transaction($TXN_ID = FALSE, $status_of_payment = null) {
123 123
 		// all payments for a TXN ordered chronologically
124
-		$query_params = array( array( 'TXN_ID' => $TXN_ID ), 'order_by' => array( 'PAY_timestamp' => 'ASC' ));
124
+		$query_params = array(array('TXN_ID' => $TXN_ID), 'order_by' => array('PAY_timestamp' => 'ASC'));
125 125
 		// if provided with a status, search specifically for that status. Otherwise get them all
126
-		if ( $status_of_payment ){
126
+		if ($status_of_payment) {
127 127
 			$query_params[0]['STS_ID'] = $status_of_payment;
128 128
 		}
129 129
 		// retrieve payments
130
-		return $this->get_all ( $query_params );
130
+		return $this->get_all($query_params);
131 131
 	}
132 132
 
133 133
 
@@ -137,8 +137,8 @@  discard block
 block discarded – undo
137 137
 	 * @param int $TXN_ID
138 138
 	 * @return EE_Payment[]
139 139
 	 */
140
-	public function get_approved_payments_for_transaction( $TXN_ID = 0 ) {
141
-		return $this->get_payments_for_transaction( $TXN_ID, EEM_Payment::status_id_approved );
140
+	public function get_approved_payments_for_transaction($TXN_ID = 0) {
141
+		return $this->get_payments_for_transaction($TXN_ID, EEM_Payment::status_id_approved);
142 142
 
143 143
 	}
144 144
 
@@ -159,36 +159,36 @@  discard block
 block discarded – undo
159 159
 	 *
160 160
 	 * @return EE_Payment[]
161 161
 	 */
162
-	public function get_payments_made_between_dates( $start_date = '', $end_date = '', $format = '', $timezone = '' ) {
163
-		$timezone = empty( $timezone ) ? EEH_DTT_Helper::get_timezone() : $timezone;
162
+	public function get_payments_made_between_dates($start_date = '', $end_date = '', $format = '', $timezone = '') {
163
+		$timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
164 164
 		//if $start_date or $end date, verify $format is included.
165
-		if ( ( ! empty( $start_date ) || ! empty( $end_date ) ) && empty( $format ) ) {
166
-			throw new EE_Error( __('You included a start date and/or a end date for this method but did not include a format string.  The format string is needed for setting up the query', 'event_espresso' ) );
165
+		if (( ! empty($start_date) || ! empty($end_date)) && empty($format)) {
166
+			throw new EE_Error(__('You included a start date and/or a end date for this method but did not include a format string.  The format string is needed for setting up the query', 'event_espresso'));
167 167
 		}
168
-		$now = new DateTime( 'now' );
168
+		$now = new DateTime('now');
169 169
 		// setup timezone objects once
170
-		$modelDateTimeZone = new DateTimeZone( $this->_timezone );
171
-		$passedDateTimeZone = new DateTimeZone( $timezone );
170
+		$modelDateTimeZone = new DateTimeZone($this->_timezone);
171
+		$passedDateTimeZone = new DateTimeZone($timezone);
172 172
 		// setup start date
173
-		$start_date = ! empty( $start_date ) ? date_create_from_format( $format, $start_date, $passedDateTimeZone ) : $now;
173
+		$start_date = ! empty($start_date) ? date_create_from_format($format, $start_date, $passedDateTimeZone) : $now;
174 174
 		EEH_DTT_Helper::setTimezone($start_date, $modelDateTimeZone);
175
-		$start_date = $start_date->format( 'Y-m-d' ) . ' 00:00:00';
176
-		$start_date = strtotime( $start_date );
175
+		$start_date = $start_date->format('Y-m-d').' 00:00:00';
176
+		$start_date = strtotime($start_date);
177 177
 		// setup end date
178
-		$end_date = ! empty( $end_date ) ? date_create_from_format( $format, $end_date, $passedDateTimeZone ) : $now;
178
+		$end_date = ! empty($end_date) ? date_create_from_format($format, $end_date, $passedDateTimeZone) : $now;
179 179
 		EEH_DTT_Helper::setTimezone($end_date, $modelDateTimeZone);
180
-		$end_date = $end_date->format('Y-m-d') . ' 23:59:59';
181
-		$end_date = strtotime( $end_date );
180
+		$end_date = $end_date->format('Y-m-d').' 23:59:59';
181
+		$end_date = strtotime($end_date);
182 182
 
183 183
 		// make sure our start date is the lowest value and vice versa
184
-		$start = min( $start_date, $end_date );
185
-		$end = max( $start_date, $end_date );
184
+		$start = min($start_date, $end_date);
185
+		$end = max($start_date, $end_date);
186 186
 
187 187
 		//yes we generated the date and time string in utc but we WANT this start date and time used in the set timezone on the model.
188
-		$start_date = $this->convert_datetime_for_query( 'PAY_timestamp', date( 'Y-m-d', $start ) . ' 00:00:00', 'Y-m-d H:i:s', $this->get_timezone() );
189
-		$end_date = $this->convert_datetime_for_query( 'PAY_timestamp', date( 'Y-m-d', $end) . ' 23:59:59' , 'Y-m-d H:i:s', $this->get_timezone() );
188
+		$start_date = $this->convert_datetime_for_query('PAY_timestamp', date('Y-m-d', $start).' 00:00:00', 'Y-m-d H:i:s', $this->get_timezone());
189
+		$end_date = $this->convert_datetime_for_query('PAY_timestamp', date('Y-m-d', $end).' 23:59:59', 'Y-m-d H:i:s', $this->get_timezone());
190 190
 
191
-		return $this->get_all(array(array('PAY_timestamp'=>array('>=',$start_date),'PAY_timestamp*'=>array('<=',$end_date))));
191
+		return $this->get_all(array(array('PAY_timestamp'=>array('>=', $start_date), 'PAY_timestamp*'=>array('<=', $end_date))));
192 192
 	}
193 193
 
194 194
 	/**
@@ -198,35 +198,35 @@  discard block
 block discarded – undo
198 198
 	 * returns a string for the approved status
199 199
 	 * @return 	string
200 200
 	 */
201
-	function approved_status(){
201
+	function approved_status() {
202 202
 		return self::status_id_approved;
203 203
 	}
204 204
 	/**
205 205
 	 * returns a string for the pending status
206 206
 	 * @return 	string
207 207
 	 */
208
-	function pending_status(){
208
+	function pending_status() {
209 209
 		return self::status_id_pending;
210 210
 	}
211 211
 	/**
212 212
 	 * returns a string for the cancelled status
213 213
 	 * @return 	string
214 214
 	 */
215
-	function cancelled_status(){
215
+	function cancelled_status() {
216 216
 		return self::status_id_cancelled;
217 217
 	}
218 218
 	/**
219 219
 	 * returns a string for the failed status
220 220
 	 * @return 	string
221 221
 	 */
222
-	function failed_status(){
222
+	function failed_status() {
223 223
 		return self::status_id_failed;
224 224
 	}
225 225
 	/**
226 226
 	 * returns a string for the declined status
227 227
 	 * @return 	string
228 228
 	 */
229
-	function declined_status(){
229
+	function declined_status() {
230 230
 		return self::status_id_declined;
231 231
 	}
232 232
 
Please login to merge, or discard this patch.
core/db_models/fields/EE_Datetime_Field.php 2 patches
Indentation   +742 added lines, -742 removed lines patch added patch discarded remove patch
@@ -17,747 +17,747 @@
 block discarded – undo
17 17
 class EE_Datetime_Field extends EE_Model_Field_Base
18 18
 {
19 19
 
20
-    /**
21
-     * The pattern we're looking for is if only the characters 0-9 are found and there are only
22
-     * 10 or more numbers (because 9 numbers even with all 9's would be sometime in 2001 )
23
-     *
24
-     * @type string unix_timestamp_regex
25
-     */
26
-    const unix_timestamp_regex = '/[0-9]{10,}/';
27
-
28
-    /**
29
-     * @type string mysql_timestamp_format
30
-     */
31
-    const mysql_timestamp_format = 'Y-m-d H:i:s';
32
-
33
-    /**
34
-     * @type string mysql_date_format
35
-     */
36
-    const mysql_date_format = 'Y-m-d';
37
-
38
-    /**
39
-     * @type string mysql_time_format
40
-     */
41
-    const mysql_time_format = 'H:i:s';
42
-
43
-    /**
44
-     * Const for using in the default value. If the field's default is set to this,
45
-     * then we will return the time of calling `get_default_value()`, not
46
-     * just the current time at construction
47
-     */
48
-    const now = 'now';
49
-
50
-    /**
51
-     * The following properties hold the default formats for date and time.
52
-     * Defaults are set via the constructor and can be overridden on class instantiation.
53
-     * However they can also be overridden later by the set_format() method
54
-     * (and corresponding set_date_format, set_time_format methods);
55
-     */
56
-    /**
57
-     * @type string $_date_format
58
-     */
59
-    protected $_date_format = '';
60
-
61
-    /**
62
-     * @type string $_time_format
63
-     */
64
-    protected $_time_format = '';
65
-
66
-    /**
67
-     * @type string $_pretty_date_format
68
-     */
69
-    protected $_pretty_date_format = '';
70
-
71
-    /**
72
-     * @type string $_pretty_time_format
73
-     */
74
-    protected $_pretty_time_format = '';
75
-
76
-    /**
77
-     * @type DateTimeZone $_DateTimeZone
78
-     */
79
-    protected $_DateTimeZone;
80
-
81
-    /**
82
-     * @type DateTimeZone $_UTC_DateTimeZone
83
-     */
84
-    protected $_UTC_DateTimeZone;
85
-
86
-    /**
87
-     * @type DateTimeZone $_blog_DateTimeZone
88
-     */
89
-    protected $_blog_DateTimeZone;
90
-
91
-
92
-    /**
93
-     * This property holds how we want the output returned when getting a datetime string.  It is set for the
94
-     * set_date_time_output() method.  By default this is empty.  When empty, we are assuming that we want both date
95
-     * and time returned via getters.
96
-     *
97
-     * @var mixed (null|string)
98
-     */
99
-    protected $_date_time_output;
100
-
101
-
102
-    /**
103
-     * timezone string
104
-     * This gets set by the constructor and can be changed by the "set_timezone()" method so that we know what timezone
105
-     * incoming strings|timestamps are in.  This can also be used before a get to set what timezone you want strings
106
-     * coming out of the object to be in.  Default timezone is the current WP timezone option setting
107
-     *
108
-     * @var string
109
-     */
110
-    protected $_timezone_string;
111
-
112
-
113
-    /**
114
-     * This holds whatever UTC offset for the blog (we automatically convert timezone strings into their related
115
-     * offsets for comparison purposes).
116
-     *
117
-     * @var int
118
-     */
119
-    protected $_blog_offset;
120
-
121
-
122
-
123
-    /**
124
-     * @param string $table_column
125
-     * @param string $nice_name
126
-     * @param bool   $nullable
127
-     * @param string $default_value
128
-     * @param string $timezone_string
129
-     * @param string $date_format
130
-     * @param string $time_format
131
-     * @param string $pretty_date_format
132
-     * @param string $pretty_time_format
133
-     * @throws EE_Error
134
-     * @throws InvalidArgumentException
135
-     */
136
-    public function __construct(
137
-        $table_column,
138
-        $nice_name,
139
-        $nullable,
140
-        $default_value,
141
-        $timezone_string = '',
142
-        $date_format = '',
143
-        $time_format = '',
144
-        $pretty_date_format = '',
145
-        $pretty_time_format = ''
146
-    ) {
147
-
148
-        $this->_date_format        = ! empty($date_format) ? $date_format : get_option('date_format');
149
-        $this->_time_format        = ! empty($time_format) ? $time_format : get_option('time_format');
150
-        $this->_pretty_date_format = ! empty($pretty_date_format) ? $pretty_date_format : get_option('date_format');
151
-        $this->_pretty_time_format = ! empty($pretty_time_format) ? $pretty_time_format : get_option('time_format');
152
-
153
-        parent::__construct($table_column, $nice_name, $nullable, $default_value);
154
-        $this->set_timezone($timezone_string);
155
-        $this->setSchemaFormat('date-time');
156
-    }
157
-
158
-
159
-    /**
160
-     * @return DateTimeZone
161
-     * @throws \EE_Error
162
-     */
163
-    public function get_UTC_DateTimeZone()
164
-    {
165
-        return $this->_UTC_DateTimeZone instanceof DateTimeZone
166
-            ? $this->_UTC_DateTimeZone
167
-            : $this->_create_timezone_object_from_timezone_string('UTC');
168
-    }
169
-
170
-
171
-    /**
172
-     * @return DateTimeZone
173
-     * @throws \EE_Error
174
-     */
175
-    public function get_blog_DateTimeZone()
176
-    {
177
-        return $this->_blog_DateTimeZone instanceof DateTimeZone
178
-            ? $this->_blog_DateTimeZone
179
-            : $this->_create_timezone_object_from_timezone_string('');
180
-    }
181
-
182
-
183
-    /**
184
-     * this prepares any incoming date data and make sure its converted to a utc unix timestamp
185
-     *
186
-     * @param  string|int $value_inputted_for_field_on_model_object could be a string formatted date time or int unix
187
-     *                                                              timestamp
188
-     * @return DateTime
189
-     */
190
-    public function prepare_for_set($value_inputted_for_field_on_model_object)
191
-    {
192
-        return $this->_get_date_object($value_inputted_for_field_on_model_object);
193
-    }
194
-
195
-
196
-    /**
197
-     * This returns the format string to be used by getters depending on what the $_date_time_output property is set at.
198
-     * getters need to know whether we're just returning the date or the time or both.  By default we return both.
199
-     *
200
-     * @param bool $pretty If we're returning the pretty formats or standard format string.
201
-     * @return string    The final assembled format string.
202
-     */
203
-    protected function _get_date_time_output($pretty = false)
204
-    {
205
-
206
-        switch ($this->_date_time_output) {
207
-            case 'time' :
208
-                return $pretty ? $this->_pretty_time_format : $this->_time_format;
209
-                break;
210
-
211
-            case 'date' :
212
-                return $pretty ? $this->_pretty_date_format : $this->_date_format;
213
-                break;
214
-
215
-            default :
216
-                return $pretty
217
-                    ? $this->_pretty_date_format . ' ' . $this->_pretty_time_format
218
-                    : $this->_date_format . ' ' . $this->_time_format;
219
-        }
220
-    }
221
-
222
-
223
-    /**
224
-     * This just sets the $_date_time_output property so we can flag how date and times are formatted before being
225
-     * returned (using the format properties)
226
-     *
227
-     * @param string $what acceptable values are 'time' or 'date'.
228
-     *                     Any other value will be set but will always result
229
-     *                     in both 'date' and 'time' being returned.
230
-     * @return void
231
-     */
232
-    public function set_date_time_output($what = null)
233
-    {
234
-        $this->_date_time_output = $what;
235
-    }
236
-
237
-
238
-    /**
239
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
240
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
241
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp).
242
-     * We also set some other properties in this method.
243
-     *
244
-     * @param string $timezone_string A valid timezone string as described by @link
245
-     *                                http://www.php.net/manual/en/timezones.php
246
-     * @return void
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidDataTypeException
249
-     * @throws InvalidInterfaceException
250
-     */
251
-    public function set_timezone($timezone_string)
252
-    {
253
-        if (empty($timezone_string) && $this->_timezone_string !== null) {
254
-            // leave the timezone AS-IS if we already have one and
255
-            // the function arg didn't provide one
256
-            return;
257
-        }
258
-        $timezone_string        = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
259
-        $this->_timezone_string = ! empty($timezone_string) ? $timezone_string : 'UTC';
260
-        $this->_DateTimeZone    = $this->_create_timezone_object_from_timezone_string($this->_timezone_string);
261
-    }
262
-
263
-
264
-    /**
265
-     * _create_timezone_object_from_timezone_name
266
-     *
267
-     * @access protected
268
-     * @param string $timezone_string
269
-     * @return \DateTimeZone
270
-     * @throws InvalidArgumentException
271
-     * @throws InvalidDataTypeException
272
-     * @throws InvalidInterfaceException
273
-     */
274
-    protected function _create_timezone_object_from_timezone_string($timezone_string = '')
275
-    {
276
-        return new DateTimeZone(EEH_DTT_Helper::get_valid_timezone_string($timezone_string));
277
-    }
278
-
279
-
280
-    /**
281
-     * This just returns whatever is set for the current timezone.
282
-     *
283
-     * @access public
284
-     * @return string timezone string
285
-     */
286
-    public function get_timezone()
287
-    {
288
-        return $this->_timezone_string;
289
-    }
290
-
291
-
292
-    /**
293
-     * set the $_date_format property
294
-     *
295
-     * @access public
296
-     * @param string $format a new date format (corresponding to formats accepted by PHP date() function)
297
-     * @param bool   $pretty Whether to set pretty format or not.
298
-     * @return void
299
-     */
300
-    public function set_date_format($format, $pretty = false)
301
-    {
302
-        if ($pretty) {
303
-            $this->_pretty_date_format = $format;
304
-        } else {
305
-            $this->_date_format = $format;
306
-        }
307
-    }
308
-
309
-
310
-    /**
311
-     * return the $_date_format property value.
312
-     *
313
-     * @param bool $pretty Whether to get pretty format or not.
314
-     * @return string
315
-     */
316
-    public function get_date_format($pretty = false)
317
-    {
318
-        return $pretty ? $this->_pretty_date_format : $this->_date_format;
319
-    }
320
-
321
-
322
-    /**
323
-     * set the $_time_format property
324
-     *
325
-     * @access public
326
-     * @param string $format a new time format (corresponding to formats accepted by PHP date() function)
327
-     * @param bool   $pretty Whether to set pretty format or not.
328
-     * @return void
329
-     */
330
-    public function set_time_format($format, $pretty = false)
331
-    {
332
-        if ($pretty) {
333
-            $this->_pretty_time_format = $format;
334
-        } else {
335
-            $this->_time_format = $format;
336
-        }
337
-    }
338
-
339
-
340
-    /**
341
-     * return the $_time_format property value.
342
-     *
343
-     * @param bool $pretty Whether to get pretty format or not.
344
-     * @return string
345
-     */
346
-    public function get_time_format($pretty = false)
347
-    {
348
-        return $pretty ? $this->_pretty_time_format : $this->_time_format;
349
-    }
350
-
351
-
352
-    /**
353
-     * set the $_pretty_date_format property
354
-     *
355
-     * @access public
356
-     * @param string $format a new pretty date format (corresponding to formats accepted by PHP date() function)
357
-     * @return void
358
-     */
359
-    public function set_pretty_date_format($format)
360
-    {
361
-        $this->_pretty_date_format = $format;
362
-    }
363
-
364
-
365
-    /**
366
-     * set the $_pretty_time_format property
367
-     *
368
-     * @access public
369
-     * @param string $format a new pretty time format (corresponding to formats accepted by PHP date() function)
370
-     * @return void
371
-     */
372
-    public function set_pretty_time_format($format)
373
-    {
374
-        $this->_pretty_time_format = $format;
375
-    }
376
-
377
-
378
-    /**
379
-     * Only sets the time portion of the datetime.
380
-     *
381
-     * @param string|DateTime $time_to_set_string like 8am OR a DateTime object.
382
-     * @param DateTime        $current            current DateTime object for the datetime field
383
-     * @return DateTime
384
-     */
385
-    public function prepare_for_set_with_new_time($time_to_set_string, DateTime $current)
386
-    {
387
-        // if $time_to_set_string is datetime object, then let's use it to set the parse array.
388
-        // Otherwise parse the string.
389
-        if ($time_to_set_string instanceof DateTime) {
390
-            $parsed = array(
391
-                'hour'   => $time_to_set_string->format('H'),
392
-                'minute' => $time_to_set_string->format('i'),
393
-                'second' => $time_to_set_string->format('s'),
394
-            );
395
-        } else {
396
-            //parse incoming string
397
-            $parsed = date_parse_from_format($this->_time_format, $time_to_set_string);
398
-        }
399
-        EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
400
-        return $current->setTime($parsed['hour'], $parsed['minute'], $parsed['second']);
401
-    }
402
-
403
-
404
-    /**
405
-     * Only sets the date portion of the datetime.
406
-     *
407
-     * @param string|DateTime $date_to_set_string like Friday, January 8th or a DateTime object.
408
-     * @param DateTime        $current            current DateTime object for the datetime field
409
-     * @return DateTime
410
-     */
411
-    public function prepare_for_set_with_new_date($date_to_set_string, DateTime $current)
412
-    {
413
-        // if $time_to_set_string is datetime object, then let's use it to set the parse array.
414
-        // Otherwise parse the string.
415
-        if ($date_to_set_string instanceof DateTime) {
416
-            $parsed = array(
417
-                'year'  => $date_to_set_string->format('Y'),
418
-                'month' => $date_to_set_string->format('m'),
419
-                'day'   => $date_to_set_string->format('d'),
420
-            );
421
-        } else {
422
-            //parse incoming string
423
-            $parsed = date_parse_from_format($this->_date_format, $date_to_set_string);
424
-        }
425
-        EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
426
-        return $current->setDate($parsed['year'], $parsed['month'], $parsed['day']);
427
-    }
428
-
429
-
430
-    /**
431
-     * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0 timezone).  When the
432
-     * datetime gets to this stage it should ALREADY be in UTC time
433
-     *
434
-     * @param  DateTime $DateTime
435
-     * @return string formatted date time for given timezone
436
-     * @throws \EE_Error
437
-     */
438
-    public function prepare_for_get($DateTime)
439
-    {
440
-        return $this->_prepare_for_display($DateTime);
441
-    }
442
-
443
-
444
-    /**
445
-     * This differs from prepare_for_get in that it considers whether the internal $_timezone differs
446
-     * from the set wp timezone.  If so, then it returns the datetime string formatted via
447
-     * _pretty_date_format, and _pretty_time_format.  However, it also appends a timezone
448
-     * abbreviation to the date_string.
449
-     *
450
-     * @param mixed $DateTime
451
-     * @param null  $schema
452
-     * @return string
453
-     * @throws \EE_Error
454
-     */
455
-    public function prepare_for_pretty_echoing($DateTime, $schema = null)
456
-    {
457
-        return $this->_prepare_for_display($DateTime, $schema ? $schema : true);
458
-    }
459
-
460
-
461
-    /**
462
-     * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
463
-     * timezone).
464
-     *
465
-     * @param DateTime    $DateTime
466
-     * @param bool|string $schema
467
-     * @return string
468
-     * @throws \EE_Error
469
-     */
470
-    protected function _prepare_for_display($DateTime, $schema = false)
471
-    {
472
-        if (! $DateTime instanceof DateTime) {
473
-            if ($this->_nullable) {
474
-                return '';
475
-            } else {
476
-                if (WP_DEBUG) {
477
-                    throw new EE_Error(
478
-                        sprintf(
479
-                            __(
480
-                                'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.',
481
-                                'event_espresso'
482
-                            ),
483
-                            $this->_nicename
484
-                        )
485
-                    );
486
-                } else {
487
-                    $DateTime = new DbSafeDateTime(\EE_Datetime_Field::now);
488
-                    EE_Error::add_error(
489
-                        sprintf(
490
-                            __(
491
-                                'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.  When WP_DEBUG is false, the value is set to "now" instead of throwing an exception.',
492
-                                'event_espresso'
493
-                            ),
494
-                            $this->_nicename
495
-                        )
496
-                    );
497
-                }
498
-            }
499
-        }
500
-        $format_string = $this->_get_date_time_output($schema);
501
-        EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
502
-        if ($schema) {
503
-            if ($this->_display_timezone()) {
504
-                //must be explicit because schema could equal true.
505
-                if ($schema === 'no_html') {
506
-                    $timezone_string = ' (' . $DateTime->format('T') . ')';
507
-                } else {
508
-                    $timezone_string = ' <span class="ee_dtt_timezone_string">(' . $DateTime->format('T') . ')</span>';
509
-                }
510
-            } else {
511
-                $timezone_string = '';
512
-            }
513
-
514
-            return $DateTime->format($format_string) . $timezone_string;
515
-        }
516
-        return $DateTime->format($format_string);
517
-    }
518
-
519
-
520
-    /**
521
-     * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
522
-     * timezone).
523
-     *
524
-     * @param  mixed $datetime_value u
525
-     * @return string mysql timestamp in UTC
526
-     * @throws \EE_Error
527
-     */
528
-    public function prepare_for_use_in_db($datetime_value)
529
-    {
530
-        //we allow an empty value or DateTime object, but nothing else.
531
-        if (! empty($datetime_value) && ! $datetime_value instanceof DateTime) {
532
-            throw new EE_Error(
533
-            	sprintf(
534
-            	    __(
535
-            		    'The incoming value being prepared for setting in the database must either be empty or a php 
20
+	/**
21
+	 * The pattern we're looking for is if only the characters 0-9 are found and there are only
22
+	 * 10 or more numbers (because 9 numbers even with all 9's would be sometime in 2001 )
23
+	 *
24
+	 * @type string unix_timestamp_regex
25
+	 */
26
+	const unix_timestamp_regex = '/[0-9]{10,}/';
27
+
28
+	/**
29
+	 * @type string mysql_timestamp_format
30
+	 */
31
+	const mysql_timestamp_format = 'Y-m-d H:i:s';
32
+
33
+	/**
34
+	 * @type string mysql_date_format
35
+	 */
36
+	const mysql_date_format = 'Y-m-d';
37
+
38
+	/**
39
+	 * @type string mysql_time_format
40
+	 */
41
+	const mysql_time_format = 'H:i:s';
42
+
43
+	/**
44
+	 * Const for using in the default value. If the field's default is set to this,
45
+	 * then we will return the time of calling `get_default_value()`, not
46
+	 * just the current time at construction
47
+	 */
48
+	const now = 'now';
49
+
50
+	/**
51
+	 * The following properties hold the default formats for date and time.
52
+	 * Defaults are set via the constructor and can be overridden on class instantiation.
53
+	 * However they can also be overridden later by the set_format() method
54
+	 * (and corresponding set_date_format, set_time_format methods);
55
+	 */
56
+	/**
57
+	 * @type string $_date_format
58
+	 */
59
+	protected $_date_format = '';
60
+
61
+	/**
62
+	 * @type string $_time_format
63
+	 */
64
+	protected $_time_format = '';
65
+
66
+	/**
67
+	 * @type string $_pretty_date_format
68
+	 */
69
+	protected $_pretty_date_format = '';
70
+
71
+	/**
72
+	 * @type string $_pretty_time_format
73
+	 */
74
+	protected $_pretty_time_format = '';
75
+
76
+	/**
77
+	 * @type DateTimeZone $_DateTimeZone
78
+	 */
79
+	protected $_DateTimeZone;
80
+
81
+	/**
82
+	 * @type DateTimeZone $_UTC_DateTimeZone
83
+	 */
84
+	protected $_UTC_DateTimeZone;
85
+
86
+	/**
87
+	 * @type DateTimeZone $_blog_DateTimeZone
88
+	 */
89
+	protected $_blog_DateTimeZone;
90
+
91
+
92
+	/**
93
+	 * This property holds how we want the output returned when getting a datetime string.  It is set for the
94
+	 * set_date_time_output() method.  By default this is empty.  When empty, we are assuming that we want both date
95
+	 * and time returned via getters.
96
+	 *
97
+	 * @var mixed (null|string)
98
+	 */
99
+	protected $_date_time_output;
100
+
101
+
102
+	/**
103
+	 * timezone string
104
+	 * This gets set by the constructor and can be changed by the "set_timezone()" method so that we know what timezone
105
+	 * incoming strings|timestamps are in.  This can also be used before a get to set what timezone you want strings
106
+	 * coming out of the object to be in.  Default timezone is the current WP timezone option setting
107
+	 *
108
+	 * @var string
109
+	 */
110
+	protected $_timezone_string;
111
+
112
+
113
+	/**
114
+	 * This holds whatever UTC offset for the blog (we automatically convert timezone strings into their related
115
+	 * offsets for comparison purposes).
116
+	 *
117
+	 * @var int
118
+	 */
119
+	protected $_blog_offset;
120
+
121
+
122
+
123
+	/**
124
+	 * @param string $table_column
125
+	 * @param string $nice_name
126
+	 * @param bool   $nullable
127
+	 * @param string $default_value
128
+	 * @param string $timezone_string
129
+	 * @param string $date_format
130
+	 * @param string $time_format
131
+	 * @param string $pretty_date_format
132
+	 * @param string $pretty_time_format
133
+	 * @throws EE_Error
134
+	 * @throws InvalidArgumentException
135
+	 */
136
+	public function __construct(
137
+		$table_column,
138
+		$nice_name,
139
+		$nullable,
140
+		$default_value,
141
+		$timezone_string = '',
142
+		$date_format = '',
143
+		$time_format = '',
144
+		$pretty_date_format = '',
145
+		$pretty_time_format = ''
146
+	) {
147
+
148
+		$this->_date_format        = ! empty($date_format) ? $date_format : get_option('date_format');
149
+		$this->_time_format        = ! empty($time_format) ? $time_format : get_option('time_format');
150
+		$this->_pretty_date_format = ! empty($pretty_date_format) ? $pretty_date_format : get_option('date_format');
151
+		$this->_pretty_time_format = ! empty($pretty_time_format) ? $pretty_time_format : get_option('time_format');
152
+
153
+		parent::__construct($table_column, $nice_name, $nullable, $default_value);
154
+		$this->set_timezone($timezone_string);
155
+		$this->setSchemaFormat('date-time');
156
+	}
157
+
158
+
159
+	/**
160
+	 * @return DateTimeZone
161
+	 * @throws \EE_Error
162
+	 */
163
+	public function get_UTC_DateTimeZone()
164
+	{
165
+		return $this->_UTC_DateTimeZone instanceof DateTimeZone
166
+			? $this->_UTC_DateTimeZone
167
+			: $this->_create_timezone_object_from_timezone_string('UTC');
168
+	}
169
+
170
+
171
+	/**
172
+	 * @return DateTimeZone
173
+	 * @throws \EE_Error
174
+	 */
175
+	public function get_blog_DateTimeZone()
176
+	{
177
+		return $this->_blog_DateTimeZone instanceof DateTimeZone
178
+			? $this->_blog_DateTimeZone
179
+			: $this->_create_timezone_object_from_timezone_string('');
180
+	}
181
+
182
+
183
+	/**
184
+	 * this prepares any incoming date data and make sure its converted to a utc unix timestamp
185
+	 *
186
+	 * @param  string|int $value_inputted_for_field_on_model_object could be a string formatted date time or int unix
187
+	 *                                                              timestamp
188
+	 * @return DateTime
189
+	 */
190
+	public function prepare_for_set($value_inputted_for_field_on_model_object)
191
+	{
192
+		return $this->_get_date_object($value_inputted_for_field_on_model_object);
193
+	}
194
+
195
+
196
+	/**
197
+	 * This returns the format string to be used by getters depending on what the $_date_time_output property is set at.
198
+	 * getters need to know whether we're just returning the date or the time or both.  By default we return both.
199
+	 *
200
+	 * @param bool $pretty If we're returning the pretty formats or standard format string.
201
+	 * @return string    The final assembled format string.
202
+	 */
203
+	protected function _get_date_time_output($pretty = false)
204
+	{
205
+
206
+		switch ($this->_date_time_output) {
207
+			case 'time' :
208
+				return $pretty ? $this->_pretty_time_format : $this->_time_format;
209
+				break;
210
+
211
+			case 'date' :
212
+				return $pretty ? $this->_pretty_date_format : $this->_date_format;
213
+				break;
214
+
215
+			default :
216
+				return $pretty
217
+					? $this->_pretty_date_format . ' ' . $this->_pretty_time_format
218
+					: $this->_date_format . ' ' . $this->_time_format;
219
+		}
220
+	}
221
+
222
+
223
+	/**
224
+	 * This just sets the $_date_time_output property so we can flag how date and times are formatted before being
225
+	 * returned (using the format properties)
226
+	 *
227
+	 * @param string $what acceptable values are 'time' or 'date'.
228
+	 *                     Any other value will be set but will always result
229
+	 *                     in both 'date' and 'time' being returned.
230
+	 * @return void
231
+	 */
232
+	public function set_date_time_output($what = null)
233
+	{
234
+		$this->_date_time_output = $what;
235
+	}
236
+
237
+
238
+	/**
239
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
240
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
241
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp).
242
+	 * We also set some other properties in this method.
243
+	 *
244
+	 * @param string $timezone_string A valid timezone string as described by @link
245
+	 *                                http://www.php.net/manual/en/timezones.php
246
+	 * @return void
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws InvalidInterfaceException
250
+	 */
251
+	public function set_timezone($timezone_string)
252
+	{
253
+		if (empty($timezone_string) && $this->_timezone_string !== null) {
254
+			// leave the timezone AS-IS if we already have one and
255
+			// the function arg didn't provide one
256
+			return;
257
+		}
258
+		$timezone_string        = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
259
+		$this->_timezone_string = ! empty($timezone_string) ? $timezone_string : 'UTC';
260
+		$this->_DateTimeZone    = $this->_create_timezone_object_from_timezone_string($this->_timezone_string);
261
+	}
262
+
263
+
264
+	/**
265
+	 * _create_timezone_object_from_timezone_name
266
+	 *
267
+	 * @access protected
268
+	 * @param string $timezone_string
269
+	 * @return \DateTimeZone
270
+	 * @throws InvalidArgumentException
271
+	 * @throws InvalidDataTypeException
272
+	 * @throws InvalidInterfaceException
273
+	 */
274
+	protected function _create_timezone_object_from_timezone_string($timezone_string = '')
275
+	{
276
+		return new DateTimeZone(EEH_DTT_Helper::get_valid_timezone_string($timezone_string));
277
+	}
278
+
279
+
280
+	/**
281
+	 * This just returns whatever is set for the current timezone.
282
+	 *
283
+	 * @access public
284
+	 * @return string timezone string
285
+	 */
286
+	public function get_timezone()
287
+	{
288
+		return $this->_timezone_string;
289
+	}
290
+
291
+
292
+	/**
293
+	 * set the $_date_format property
294
+	 *
295
+	 * @access public
296
+	 * @param string $format a new date format (corresponding to formats accepted by PHP date() function)
297
+	 * @param bool   $pretty Whether to set pretty format or not.
298
+	 * @return void
299
+	 */
300
+	public function set_date_format($format, $pretty = false)
301
+	{
302
+		if ($pretty) {
303
+			$this->_pretty_date_format = $format;
304
+		} else {
305
+			$this->_date_format = $format;
306
+		}
307
+	}
308
+
309
+
310
+	/**
311
+	 * return the $_date_format property value.
312
+	 *
313
+	 * @param bool $pretty Whether to get pretty format or not.
314
+	 * @return string
315
+	 */
316
+	public function get_date_format($pretty = false)
317
+	{
318
+		return $pretty ? $this->_pretty_date_format : $this->_date_format;
319
+	}
320
+
321
+
322
+	/**
323
+	 * set the $_time_format property
324
+	 *
325
+	 * @access public
326
+	 * @param string $format a new time format (corresponding to formats accepted by PHP date() function)
327
+	 * @param bool   $pretty Whether to set pretty format or not.
328
+	 * @return void
329
+	 */
330
+	public function set_time_format($format, $pretty = false)
331
+	{
332
+		if ($pretty) {
333
+			$this->_pretty_time_format = $format;
334
+		} else {
335
+			$this->_time_format = $format;
336
+		}
337
+	}
338
+
339
+
340
+	/**
341
+	 * return the $_time_format property value.
342
+	 *
343
+	 * @param bool $pretty Whether to get pretty format or not.
344
+	 * @return string
345
+	 */
346
+	public function get_time_format($pretty = false)
347
+	{
348
+		return $pretty ? $this->_pretty_time_format : $this->_time_format;
349
+	}
350
+
351
+
352
+	/**
353
+	 * set the $_pretty_date_format property
354
+	 *
355
+	 * @access public
356
+	 * @param string $format a new pretty date format (corresponding to formats accepted by PHP date() function)
357
+	 * @return void
358
+	 */
359
+	public function set_pretty_date_format($format)
360
+	{
361
+		$this->_pretty_date_format = $format;
362
+	}
363
+
364
+
365
+	/**
366
+	 * set the $_pretty_time_format property
367
+	 *
368
+	 * @access public
369
+	 * @param string $format a new pretty time format (corresponding to formats accepted by PHP date() function)
370
+	 * @return void
371
+	 */
372
+	public function set_pretty_time_format($format)
373
+	{
374
+		$this->_pretty_time_format = $format;
375
+	}
376
+
377
+
378
+	/**
379
+	 * Only sets the time portion of the datetime.
380
+	 *
381
+	 * @param string|DateTime $time_to_set_string like 8am OR a DateTime object.
382
+	 * @param DateTime        $current            current DateTime object for the datetime field
383
+	 * @return DateTime
384
+	 */
385
+	public function prepare_for_set_with_new_time($time_to_set_string, DateTime $current)
386
+	{
387
+		// if $time_to_set_string is datetime object, then let's use it to set the parse array.
388
+		// Otherwise parse the string.
389
+		if ($time_to_set_string instanceof DateTime) {
390
+			$parsed = array(
391
+				'hour'   => $time_to_set_string->format('H'),
392
+				'minute' => $time_to_set_string->format('i'),
393
+				'second' => $time_to_set_string->format('s'),
394
+			);
395
+		} else {
396
+			//parse incoming string
397
+			$parsed = date_parse_from_format($this->_time_format, $time_to_set_string);
398
+		}
399
+		EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
400
+		return $current->setTime($parsed['hour'], $parsed['minute'], $parsed['second']);
401
+	}
402
+
403
+
404
+	/**
405
+	 * Only sets the date portion of the datetime.
406
+	 *
407
+	 * @param string|DateTime $date_to_set_string like Friday, January 8th or a DateTime object.
408
+	 * @param DateTime        $current            current DateTime object for the datetime field
409
+	 * @return DateTime
410
+	 */
411
+	public function prepare_for_set_with_new_date($date_to_set_string, DateTime $current)
412
+	{
413
+		// if $time_to_set_string is datetime object, then let's use it to set the parse array.
414
+		// Otherwise parse the string.
415
+		if ($date_to_set_string instanceof DateTime) {
416
+			$parsed = array(
417
+				'year'  => $date_to_set_string->format('Y'),
418
+				'month' => $date_to_set_string->format('m'),
419
+				'day'   => $date_to_set_string->format('d'),
420
+			);
421
+		} else {
422
+			//parse incoming string
423
+			$parsed = date_parse_from_format($this->_date_format, $date_to_set_string);
424
+		}
425
+		EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
426
+		return $current->setDate($parsed['year'], $parsed['month'], $parsed['day']);
427
+	}
428
+
429
+
430
+	/**
431
+	 * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0 timezone).  When the
432
+	 * datetime gets to this stage it should ALREADY be in UTC time
433
+	 *
434
+	 * @param  DateTime $DateTime
435
+	 * @return string formatted date time for given timezone
436
+	 * @throws \EE_Error
437
+	 */
438
+	public function prepare_for_get($DateTime)
439
+	{
440
+		return $this->_prepare_for_display($DateTime);
441
+	}
442
+
443
+
444
+	/**
445
+	 * This differs from prepare_for_get in that it considers whether the internal $_timezone differs
446
+	 * from the set wp timezone.  If so, then it returns the datetime string formatted via
447
+	 * _pretty_date_format, and _pretty_time_format.  However, it also appends a timezone
448
+	 * abbreviation to the date_string.
449
+	 *
450
+	 * @param mixed $DateTime
451
+	 * @param null  $schema
452
+	 * @return string
453
+	 * @throws \EE_Error
454
+	 */
455
+	public function prepare_for_pretty_echoing($DateTime, $schema = null)
456
+	{
457
+		return $this->_prepare_for_display($DateTime, $schema ? $schema : true);
458
+	}
459
+
460
+
461
+	/**
462
+	 * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
463
+	 * timezone).
464
+	 *
465
+	 * @param DateTime    $DateTime
466
+	 * @param bool|string $schema
467
+	 * @return string
468
+	 * @throws \EE_Error
469
+	 */
470
+	protected function _prepare_for_display($DateTime, $schema = false)
471
+	{
472
+		if (! $DateTime instanceof DateTime) {
473
+			if ($this->_nullable) {
474
+				return '';
475
+			} else {
476
+				if (WP_DEBUG) {
477
+					throw new EE_Error(
478
+						sprintf(
479
+							__(
480
+								'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.',
481
+								'event_espresso'
482
+							),
483
+							$this->_nicename
484
+						)
485
+					);
486
+				} else {
487
+					$DateTime = new DbSafeDateTime(\EE_Datetime_Field::now);
488
+					EE_Error::add_error(
489
+						sprintf(
490
+							__(
491
+								'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.  When WP_DEBUG is false, the value is set to "now" instead of throwing an exception.',
492
+								'event_espresso'
493
+							),
494
+							$this->_nicename
495
+						)
496
+					);
497
+				}
498
+			}
499
+		}
500
+		$format_string = $this->_get_date_time_output($schema);
501
+		EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
502
+		if ($schema) {
503
+			if ($this->_display_timezone()) {
504
+				//must be explicit because schema could equal true.
505
+				if ($schema === 'no_html') {
506
+					$timezone_string = ' (' . $DateTime->format('T') . ')';
507
+				} else {
508
+					$timezone_string = ' <span class="ee_dtt_timezone_string">(' . $DateTime->format('T') . ')</span>';
509
+				}
510
+			} else {
511
+				$timezone_string = '';
512
+			}
513
+
514
+			return $DateTime->format($format_string) . $timezone_string;
515
+		}
516
+		return $DateTime->format($format_string);
517
+	}
518
+
519
+
520
+	/**
521
+	 * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
522
+	 * timezone).
523
+	 *
524
+	 * @param  mixed $datetime_value u
525
+	 * @return string mysql timestamp in UTC
526
+	 * @throws \EE_Error
527
+	 */
528
+	public function prepare_for_use_in_db($datetime_value)
529
+	{
530
+		//we allow an empty value or DateTime object, but nothing else.
531
+		if (! empty($datetime_value) && ! $datetime_value instanceof DateTime) {
532
+			throw new EE_Error(
533
+				sprintf(
534
+					__(
535
+						'The incoming value being prepared for setting in the database must either be empty or a php 
536 536
             		    DateTime object, instead of: %1$s %2$s',
537
-                        'event_espresso'
538
-	                ),
539
-                    '<br />',
540
-                    print_r($datetime_value, true)
541
-                )
542
-            );
543
-        }
544
-
545
-        if ($datetime_value instanceof DateTime) {
546
-            if (! $datetime_value instanceof DbSafeDateTime) {
547
-                $datetime_value = DbSafeDateTime::createFromDateTime($datetime_value);
548
-            }
549
-            EEH_DTT_Helper::setTimezone($datetime_value, $this->get_UTC_DateTimeZone());
550
-            return $datetime_value->format(
551
-                EE_Datetime_Field::mysql_timestamp_format
552
-            );
553
-        }
554
-
555
-        // if $datetime_value is empty, and ! $this->_nullable, use current_time() but set the GMT flag to true
556
-        return ! $this->_nullable && empty($datetime_value) ? current_time('mysql', true) : null;
557
-    }
558
-
559
-
560
-    /**
561
-     * This prepares the datetime for internal usage as a PHP DateTime object OR null (if nullable is
562
-     * allowed)
563
-     *
564
-     * @param string $datetime_string mysql timestamp in UTC
565
-     * @return  mixed null | DateTime
566
-     * @throws \EE_Error
567
-     */
568
-    public function prepare_for_set_from_db($datetime_string)
569
-    {
570
-        //if $datetime_value is empty, and ! $this->_nullable, just use time()
571
-        if (empty($datetime_string) && $this->_nullable) {
572
-            return null;
573
-        }
574
-        // datetime strings from the db should ALWAYS be in UTC+0, so use UTC_DateTimeZone when creating
575
-        if (empty($datetime_string)) {
576
-            $DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
577
-        } else {
578
-            $DateTime = DateTime::createFromFormat(
579
-                EE_Datetime_Field::mysql_timestamp_format,
580
-                $datetime_string,
581
-                $this->get_UTC_DateTimeZone()
582
-            );
583
-            if ($DateTime instanceof \DateTime) {
584
-                $DateTime = new DbSafeDateTime(
585
-                    $DateTime->format(\EE_Datetime_Field::mysql_timestamp_format),
586
-                    $this->get_UTC_DateTimeZone()
587
-                );
588
-            }
589
-        }
590
-
591
-        if (! $DateTime instanceof DbSafeDateTime) {
592
-            // if still no datetime object, then let's just use now
593
-            $DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
594
-        }
595
-        // THEN apply the field's set DateTimeZone
596
-        EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
597
-        return $DateTime;
598
-    }
599
-
600
-
601
-    /**
602
-     * All this method does is determine if we're going to display the timezone string or not on any output.
603
-     * To determine this we check if the set timezone offset is different than the blog's set timezone offset.
604
-     * If so, then true.
605
-     *
606
-     * @return bool true for yes false for no
607
-     * @throws \EE_Error
608
-     */
609
-    protected function _display_timezone()
610
-    {
611
-
612
-        // first let's do a comparison of timezone strings.
613
-        // If they match then we can get out without any further calculations
614
-        $blog_string = get_option('timezone_string');
615
-        if ($blog_string === $this->_timezone_string) {
616
-            return false;
617
-        }
618
-        // now we need to calc the offset for the timezone string so we can compare with the blog offset.
619
-        $this_offset = $this->get_timezone_offset($this->_DateTimeZone);
620
-        $blog_offset = $this->get_timezone_offset($this->get_blog_DateTimeZone());
621
-        // now compare
622
-        return $blog_offset !== $this_offset;
623
-    }
624
-
625
-
626
-    /**
627
-     * This method returns a php DateTime object for setting on the EE_Base_Class model.
628
-     * EE passes around DateTime objects because they are MUCH easier to manipulate and deal
629
-     * with.
630
-     *
631
-     * @param int|string|DateTime $date_string            This should be the incoming date string.  It's assumed to be
632
-     *                                                    in the format that is set on the date_field (or DateTime
633
-     *                                                    object)!
634
-     * @return DateTime
635
-     */
636
-    protected function _get_date_object($date_string)
637
-    {
638
-        //first if this is an empty date_string and nullable is allowed, just return null.
639
-        if ($this->_nullable && empty($date_string)) {
640
-            return null;
641
-        }
642
-
643
-        // if incoming date
644
-        if ($date_string instanceof DateTime) {
645
-            EEH_DTT_Helper::setTimezone($date_string, $this->_DateTimeZone);
646
-            return $date_string;
647
-        }
648
-        // if empty date_string and made it here.
649
-        // Return a datetime object for now in the given timezone.
650
-        if (empty($date_string)) {
651
-            return new DbSafeDateTime(\EE_Datetime_Field::now, $this->_DateTimeZone);
652
-        }
653
-        // if $date_string is matches something that looks like a Unix timestamp let's just use it.
654
-        if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $date_string)) {
655
-            try {
656
-                // This is operating under the assumption that the incoming Unix timestamp
657
-                // is an ACTUAL Unix timestamp and not the calculated one output by current_time('timestamp');
658
-                $DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->_DateTimeZone);
659
-                $DateTime->setTimestamp($date_string);
660
-
661
-                return $DateTime;
662
-            } catch (Exception $e) {
663
-                // should be rare, but if things got fooled then let's just continue
664
-            }
665
-        }
666
-        //not a unix timestamp.  So we will use the set format on this object and set timezone to
667
-        //create the DateTime object.
668
-        $format = $this->_date_format . ' ' . $this->_time_format;
669
-        try {
670
-            $DateTime = DateTime::createFromFormat($format, $date_string, $this->_DateTimeZone);
671
-            if ($DateTime instanceof DateTime) {
672
-                $DateTime = new DbSafeDateTime(
673
-                    $DateTime->format(\EE_Datetime_Field::mysql_timestamp_format),
674
-                    $this->_DateTimeZone
675
-                );
676
-            }
677
-            if (! $DateTime instanceof DbSafeDateTime) {
678
-                throw new EE_Error(
679
-                    sprintf(
680
-                        __('"%1$s" does not represent a valid Date Time in the format "%2$s".', 'event_espresso'),
681
-                        $date_string,
682
-                        $format
683
-                    )
684
-                );
685
-            }
686
-        } catch (Exception $e) {
687
-            // if we made it here then likely then something went really wrong.
688
-            // Instead of throwing an exception, let's just return a DateTime object for now, in the set timezone.
689
-            $DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->_DateTimeZone);
690
-        }
691
-
692
-        return $DateTime;
693
-    }
694
-
695
-
696
-
697
-    /**
698
-     * get_timezone_transitions
699
-     *
700
-     * @param \DateTimeZone $DateTimeZone
701
-     * @param int           $time
702
-     * @param bool          $first_only
703
-     * @return mixed
704
-     */
705
-    public function get_timezone_transitions(DateTimeZone $DateTimeZone, $time = null, $first_only = true)
706
-    {
707
-        return EEH_DTT_Helper::get_timezone_transitions($DateTimeZone, $time, $first_only);
708
-    }
709
-
710
-
711
-
712
-    /**
713
-     * get_timezone_offset
714
-     *
715
-     * @param \DateTimeZone $DateTimeZone
716
-     * @param int           $time
717
-     * @return mixed
718
-     * @throws \DomainException
719
-     */
720
-    public function get_timezone_offset(DateTimeZone $DateTimeZone, $time = null)
721
-    {
722
-        return EEH_DTT_Helper::get_timezone_offset($DateTimeZone, $time);
723
-    }
724
-
725
-
726
-    /**
727
-     * This will take an incoming timezone string and return the abbreviation for that timezone
728
-     *
729
-     * @param  string $timezone_string
730
-     * @return string           abbreviation
731
-     * @throws \EE_Error
732
-     */
733
-    public function get_timezone_abbrev($timezone_string)
734
-    {
735
-        $timezone_string = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
736
-        $dateTime        = new DateTime(\EE_Datetime_Field::now, new DateTimeZone($timezone_string));
737
-
738
-        return $dateTime->format('T');
739
-    }
740
-
741
-    /**
742
-     * Overrides the parent to allow for having a dynamic "now" value
743
-     *
744
-     * @return mixed
745
-     */
746
-    public function get_default_value()
747
-    {
748
-        if ($this->_default_value === EE_Datetime_Field::now) {
749
-            return time();
750
-        } else {
751
-            return parent::get_default_value();
752
-        }
753
-    }
754
-
755
-
756
-    public function getSchemaDescription()
757
-    {
758
-        return sprintf(
759
-            esc_html__('%s - the value for this field is in the timezone of the site.', 'event_espresso'),
760
-            $this->get_nicename()
761
-        );
762
-    }
537
+						'event_espresso'
538
+					),
539
+					'<br />',
540
+					print_r($datetime_value, true)
541
+				)
542
+			);
543
+		}
544
+
545
+		if ($datetime_value instanceof DateTime) {
546
+			if (! $datetime_value instanceof DbSafeDateTime) {
547
+				$datetime_value = DbSafeDateTime::createFromDateTime($datetime_value);
548
+			}
549
+			EEH_DTT_Helper::setTimezone($datetime_value, $this->get_UTC_DateTimeZone());
550
+			return $datetime_value->format(
551
+				EE_Datetime_Field::mysql_timestamp_format
552
+			);
553
+		}
554
+
555
+		// if $datetime_value is empty, and ! $this->_nullable, use current_time() but set the GMT flag to true
556
+		return ! $this->_nullable && empty($datetime_value) ? current_time('mysql', true) : null;
557
+	}
558
+
559
+
560
+	/**
561
+	 * This prepares the datetime for internal usage as a PHP DateTime object OR null (if nullable is
562
+	 * allowed)
563
+	 *
564
+	 * @param string $datetime_string mysql timestamp in UTC
565
+	 * @return  mixed null | DateTime
566
+	 * @throws \EE_Error
567
+	 */
568
+	public function prepare_for_set_from_db($datetime_string)
569
+	{
570
+		//if $datetime_value is empty, and ! $this->_nullable, just use time()
571
+		if (empty($datetime_string) && $this->_nullable) {
572
+			return null;
573
+		}
574
+		// datetime strings from the db should ALWAYS be in UTC+0, so use UTC_DateTimeZone when creating
575
+		if (empty($datetime_string)) {
576
+			$DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
577
+		} else {
578
+			$DateTime = DateTime::createFromFormat(
579
+				EE_Datetime_Field::mysql_timestamp_format,
580
+				$datetime_string,
581
+				$this->get_UTC_DateTimeZone()
582
+			);
583
+			if ($DateTime instanceof \DateTime) {
584
+				$DateTime = new DbSafeDateTime(
585
+					$DateTime->format(\EE_Datetime_Field::mysql_timestamp_format),
586
+					$this->get_UTC_DateTimeZone()
587
+				);
588
+			}
589
+		}
590
+
591
+		if (! $DateTime instanceof DbSafeDateTime) {
592
+			// if still no datetime object, then let's just use now
593
+			$DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
594
+		}
595
+		// THEN apply the field's set DateTimeZone
596
+		EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
597
+		return $DateTime;
598
+	}
599
+
600
+
601
+	/**
602
+	 * All this method does is determine if we're going to display the timezone string or not on any output.
603
+	 * To determine this we check if the set timezone offset is different than the blog's set timezone offset.
604
+	 * If so, then true.
605
+	 *
606
+	 * @return bool true for yes false for no
607
+	 * @throws \EE_Error
608
+	 */
609
+	protected function _display_timezone()
610
+	{
611
+
612
+		// first let's do a comparison of timezone strings.
613
+		// If they match then we can get out without any further calculations
614
+		$blog_string = get_option('timezone_string');
615
+		if ($blog_string === $this->_timezone_string) {
616
+			return false;
617
+		}
618
+		// now we need to calc the offset for the timezone string so we can compare with the blog offset.
619
+		$this_offset = $this->get_timezone_offset($this->_DateTimeZone);
620
+		$blog_offset = $this->get_timezone_offset($this->get_blog_DateTimeZone());
621
+		// now compare
622
+		return $blog_offset !== $this_offset;
623
+	}
624
+
625
+
626
+	/**
627
+	 * This method returns a php DateTime object for setting on the EE_Base_Class model.
628
+	 * EE passes around DateTime objects because they are MUCH easier to manipulate and deal
629
+	 * with.
630
+	 *
631
+	 * @param int|string|DateTime $date_string            This should be the incoming date string.  It's assumed to be
632
+	 *                                                    in the format that is set on the date_field (or DateTime
633
+	 *                                                    object)!
634
+	 * @return DateTime
635
+	 */
636
+	protected function _get_date_object($date_string)
637
+	{
638
+		//first if this is an empty date_string and nullable is allowed, just return null.
639
+		if ($this->_nullable && empty($date_string)) {
640
+			return null;
641
+		}
642
+
643
+		// if incoming date
644
+		if ($date_string instanceof DateTime) {
645
+			EEH_DTT_Helper::setTimezone($date_string, $this->_DateTimeZone);
646
+			return $date_string;
647
+		}
648
+		// if empty date_string and made it here.
649
+		// Return a datetime object for now in the given timezone.
650
+		if (empty($date_string)) {
651
+			return new DbSafeDateTime(\EE_Datetime_Field::now, $this->_DateTimeZone);
652
+		}
653
+		// if $date_string is matches something that looks like a Unix timestamp let's just use it.
654
+		if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $date_string)) {
655
+			try {
656
+				// This is operating under the assumption that the incoming Unix timestamp
657
+				// is an ACTUAL Unix timestamp and not the calculated one output by current_time('timestamp');
658
+				$DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->_DateTimeZone);
659
+				$DateTime->setTimestamp($date_string);
660
+
661
+				return $DateTime;
662
+			} catch (Exception $e) {
663
+				// should be rare, but if things got fooled then let's just continue
664
+			}
665
+		}
666
+		//not a unix timestamp.  So we will use the set format on this object and set timezone to
667
+		//create the DateTime object.
668
+		$format = $this->_date_format . ' ' . $this->_time_format;
669
+		try {
670
+			$DateTime = DateTime::createFromFormat($format, $date_string, $this->_DateTimeZone);
671
+			if ($DateTime instanceof DateTime) {
672
+				$DateTime = new DbSafeDateTime(
673
+					$DateTime->format(\EE_Datetime_Field::mysql_timestamp_format),
674
+					$this->_DateTimeZone
675
+				);
676
+			}
677
+			if (! $DateTime instanceof DbSafeDateTime) {
678
+				throw new EE_Error(
679
+					sprintf(
680
+						__('"%1$s" does not represent a valid Date Time in the format "%2$s".', 'event_espresso'),
681
+						$date_string,
682
+						$format
683
+					)
684
+				);
685
+			}
686
+		} catch (Exception $e) {
687
+			// if we made it here then likely then something went really wrong.
688
+			// Instead of throwing an exception, let's just return a DateTime object for now, in the set timezone.
689
+			$DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->_DateTimeZone);
690
+		}
691
+
692
+		return $DateTime;
693
+	}
694
+
695
+
696
+
697
+	/**
698
+	 * get_timezone_transitions
699
+	 *
700
+	 * @param \DateTimeZone $DateTimeZone
701
+	 * @param int           $time
702
+	 * @param bool          $first_only
703
+	 * @return mixed
704
+	 */
705
+	public function get_timezone_transitions(DateTimeZone $DateTimeZone, $time = null, $first_only = true)
706
+	{
707
+		return EEH_DTT_Helper::get_timezone_transitions($DateTimeZone, $time, $first_only);
708
+	}
709
+
710
+
711
+
712
+	/**
713
+	 * get_timezone_offset
714
+	 *
715
+	 * @param \DateTimeZone $DateTimeZone
716
+	 * @param int           $time
717
+	 * @return mixed
718
+	 * @throws \DomainException
719
+	 */
720
+	public function get_timezone_offset(DateTimeZone $DateTimeZone, $time = null)
721
+	{
722
+		return EEH_DTT_Helper::get_timezone_offset($DateTimeZone, $time);
723
+	}
724
+
725
+
726
+	/**
727
+	 * This will take an incoming timezone string and return the abbreviation for that timezone
728
+	 *
729
+	 * @param  string $timezone_string
730
+	 * @return string           abbreviation
731
+	 * @throws \EE_Error
732
+	 */
733
+	public function get_timezone_abbrev($timezone_string)
734
+	{
735
+		$timezone_string = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
736
+		$dateTime        = new DateTime(\EE_Datetime_Field::now, new DateTimeZone($timezone_string));
737
+
738
+		return $dateTime->format('T');
739
+	}
740
+
741
+	/**
742
+	 * Overrides the parent to allow for having a dynamic "now" value
743
+	 *
744
+	 * @return mixed
745
+	 */
746
+	public function get_default_value()
747
+	{
748
+		if ($this->_default_value === EE_Datetime_Field::now) {
749
+			return time();
750
+		} else {
751
+			return parent::get_default_value();
752
+		}
753
+	}
754
+
755
+
756
+	public function getSchemaDescription()
757
+	{
758
+		return sprintf(
759
+			esc_html__('%s - the value for this field is in the timezone of the site.', 'event_espresso'),
760
+			$this->get_nicename()
761
+		);
762
+	}
763 763
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -214,8 +214,8 @@  discard block
 block discarded – undo
214 214
 
215 215
             default :
216 216
                 return $pretty
217
-                    ? $this->_pretty_date_format . ' ' . $this->_pretty_time_format
218
-                    : $this->_date_format . ' ' . $this->_time_format;
217
+                    ? $this->_pretty_date_format.' '.$this->_pretty_time_format
218
+                    : $this->_date_format.' '.$this->_time_format;
219 219
         }
220 220
     }
221 221
 
@@ -469,7 +469,7 @@  discard block
 block discarded – undo
469 469
      */
470 470
     protected function _prepare_for_display($DateTime, $schema = false)
471 471
     {
472
-        if (! $DateTime instanceof DateTime) {
472
+        if ( ! $DateTime instanceof DateTime) {
473 473
             if ($this->_nullable) {
474 474
                 return '';
475 475
             } else {
@@ -503,15 +503,15 @@  discard block
 block discarded – undo
503 503
             if ($this->_display_timezone()) {
504 504
                 //must be explicit because schema could equal true.
505 505
                 if ($schema === 'no_html') {
506
-                    $timezone_string = ' (' . $DateTime->format('T') . ')';
506
+                    $timezone_string = ' ('.$DateTime->format('T').')';
507 507
                 } else {
508
-                    $timezone_string = ' <span class="ee_dtt_timezone_string">(' . $DateTime->format('T') . ')</span>';
508
+                    $timezone_string = ' <span class="ee_dtt_timezone_string">('.$DateTime->format('T').')</span>';
509 509
                 }
510 510
             } else {
511 511
                 $timezone_string = '';
512 512
             }
513 513
 
514
-            return $DateTime->format($format_string) . $timezone_string;
514
+            return $DateTime->format($format_string).$timezone_string;
515 515
         }
516 516
         return $DateTime->format($format_string);
517 517
     }
@@ -528,7 +528,7 @@  discard block
 block discarded – undo
528 528
     public function prepare_for_use_in_db($datetime_value)
529 529
     {
530 530
         //we allow an empty value or DateTime object, but nothing else.
531
-        if (! empty($datetime_value) && ! $datetime_value instanceof DateTime) {
531
+        if ( ! empty($datetime_value) && ! $datetime_value instanceof DateTime) {
532 532
             throw new EE_Error(
533 533
             	sprintf(
534 534
             	    __(
@@ -543,7 +543,7 @@  discard block
 block discarded – undo
543 543
         }
544 544
 
545 545
         if ($datetime_value instanceof DateTime) {
546
-            if (! $datetime_value instanceof DbSafeDateTime) {
546
+            if ( ! $datetime_value instanceof DbSafeDateTime) {
547 547
                 $datetime_value = DbSafeDateTime::createFromDateTime($datetime_value);
548 548
             }
549 549
             EEH_DTT_Helper::setTimezone($datetime_value, $this->get_UTC_DateTimeZone());
@@ -588,7 +588,7 @@  discard block
 block discarded – undo
588 588
             }
589 589
         }
590 590
 
591
-        if (! $DateTime instanceof DbSafeDateTime) {
591
+        if ( ! $DateTime instanceof DbSafeDateTime) {
592 592
             // if still no datetime object, then let's just use now
593 593
             $DateTime = new DbSafeDateTime(\EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
594 594
         }
@@ -665,7 +665,7 @@  discard block
 block discarded – undo
665 665
         }
666 666
         //not a unix timestamp.  So we will use the set format on this object and set timezone to
667 667
         //create the DateTime object.
668
-        $format = $this->_date_format . ' ' . $this->_time_format;
668
+        $format = $this->_date_format.' '.$this->_time_format;
669 669
         try {
670 670
             $DateTime = DateTime::createFromFormat($format, $date_string, $this->_DateTimeZone);
671 671
             if ($DateTime instanceof DateTime) {
@@ -674,7 +674,7 @@  discard block
 block discarded – undo
674 674
                     $this->_DateTimeZone
675 675
                 );
676 676
             }
677
-            if (! $DateTime instanceof DbSafeDateTime) {
677
+            if ( ! $DateTime instanceof DbSafeDateTime) {
678 678
                 throw new EE_Error(
679 679
                     sprintf(
680 680
                         __('"%1$s" does not represent a valid Date Time in the format "%2$s".', 'event_espresso'),
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Read.php 2 patches
Indentation   +1341 added lines, -1347 removed lines patch added patch discarded remove patch
@@ -23,7 +23,7 @@  discard block
 block discarded – undo
23 23
 use EEM_CPT_Base;
24 24
 
25 25
 if (! defined('EVENT_ESPRESSO_VERSION')) {
26
-    exit('No direct script access allowed');
26
+	exit('No direct script access allowed');
27 27
 }
28 28
 
29 29
 
@@ -41,1364 +41,1358 @@  discard block
 block discarded – undo
41 41
 
42 42
 
43 43
 
44
-    /**
45
-     * @var CalculatedModelFields
46
-     */
47
-    protected $fields_calculator;
44
+	/**
45
+	 * @var CalculatedModelFields
46
+	 */
47
+	protected $fields_calculator;
48 48
 
49 49
 
50 50
 
51
-    /**
52
-     * Read constructor.
53
-     */
54
-    public function __construct()
55
-    {
56
-        parent::__construct();
57
-        $this->fields_calculator = new CalculatedModelFields();
58
-    }
51
+	/**
52
+	 * Read constructor.
53
+	 */
54
+	public function __construct()
55
+	{
56
+		parent::__construct();
57
+		$this->fields_calculator = new CalculatedModelFields();
58
+	}
59 59
 
60 60
 
61 61
 
62
-    /**
63
-     * Handles requests to get all (or a filtered subset) of entities for a particular model
64
-
65
-     *
62
+	/**
63
+	 * Handles requests to get all (or a filtered subset) of entities for a particular model
64
+	 *
66 65
 *@param WP_REST_Request $request
67
-     * @param string           $version
68
-     * @param string           $model_name
69
-     * @return \WP_REST_Response|WP_Error
70
-     */
71
-    public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
72
-    {
73
-        $controller = new Read();
74
-        try {
75
-            $controller->setRequestedVersion($version);
76
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
77
-                return $controller->sendResponse(
78
-                    new WP_Error(
79
-                        'endpoint_parsing_error',
80
-                        sprintf(
81
-                            __(
82
-                                'There is no model for endpoint %s. Please contact event espresso support',
83
-                                'event_espresso'
84
-                            ),
85
-                            $model_name
86
-                        )
87
-                    )
88
-                );
89
-            }
90
-            return $controller->sendResponse(
91
-                $controller->getEntitiesFromModel(
92
-                    $controller->getModelVersionInfo()->loadModel($model_name),
93
-                    $request
94
-                )
95
-            );
96
-        } catch (Exception $e) {
97
-            return $controller->sendResponse($e);
98
-        }
99
-    }
100
-
101
-
102
-
103
-    /**
104
-     * Prepares and returns schema for any OPTIONS request.
105
-     *
106
-     * @param string $version    The API endpoint version being used.
107
-     * @param string $model_name Something like `Event` or `Registration`
108
-     * @return array
109
-     */
110
-    public static function handleSchemaRequest($version, $model_name)
111
-    {
112
-        $controller = new Read();
113
-        try {
114
-            $controller->setRequestedVersion($version);
115
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
116
-                return array();
117
-            }
118
-            //get the model for this version
119
-            $model = $controller->getModelVersionInfo()->loadModel($model_name);
120
-            $model_schema = new JsonModelSchema($model);
121
-            return $model_schema->getModelSchemaForRelations(
122
-                $controller->getModelVersionInfo()->relationSettings($model),
123
-                $controller->customizeSchemaForRestResponse(
124
-                    $model,
125
-                    $model_schema->getModelSchemaForFields(
126
-                        $controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
127
-                        $model_schema->getInitialSchemaStructure()
128
-                    )
129
-                )
130
-            );
131
-        } catch (Exception $e) {
132
-            return array();
133
-        }
134
-    }
135
-
136
-
137
-
138
-    /**
139
-     * This loops through each field in the given schema for the model and does the following:
140
-     * - add any extra fields that are REST API specific and related to existing fields.
141
-     * - transform default values into the correct format for a REST API response.
142
-     *
143
-     * @param EEM_Base $model
144
-     * @param array     $schema
145
-     * @return array  The final schema.
146
-     */
147
-    protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
148
-    {
149
-        foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
150
-            $schema = $this->translateDefaultsForRestResponse(
151
-                $field_name,
152
-                $field,
153
-                $this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
154
-            );
155
-        }
156
-        return $schema;
157
-    }
158
-
159
-
160
-
161
-    /**
162
-     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
163
-     * response.
164
-     *
165
-     * @param                      $field_name
166
-     * @param EE_Model_Field_Base $field
167
-     * @param array                $schema
168
-     * @return array
169
-     * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
170
-     * did, let's know about it ASAP, so let the exception bubble up)
171
-     */
172
-    protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
173
-    {
174
-        if (isset($schema['properties'][$field_name]['default'])) {
175
-            if (is_array($schema['properties'][$field_name]['default'])) {
176
-                foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
177
-                    if ($default_key === 'raw') {
178
-                        $schema['properties'][$field_name]['default'][$default_key] =
179
-                            ModelDataTranslator::prepareFieldValueForJson(
180
-                                $field,
181
-                                $default_value,
182
-                                $this->getModelVersionInfo()->requestedVersion()
183
-                            );
184
-                    }
185
-                }
186
-            } else {
187
-                $schema['properties'][$field_name]['default'] = ModelDataTranslator::prepareFieldValueForJson(
188
-                    $field,
189
-                    $schema['properties'][$field_name]['default'],
190
-                    $this->getModelVersionInfo()->requestedVersion()
191
-                );
192
-            }
193
-        }
194
-        return $schema;
195
-    }
196
-
197
-
198
-
199
-    /**
200
-     * Adds additional fields to the schema
201
-     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
202
-     * needs to be added to the schema.
203
-     *
204
-     * @param                      $field_name
205
-     * @param EE_Model_Field_Base $field
206
-     * @param array                $schema
207
-     * @return array
208
-     */
209
-    protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
210
-    {
211
-        if ($field instanceof EE_Datetime_Field) {
212
-            $schema['properties'][$field_name . '_gmt'] = $field->getSchema();
213
-            //modify the description
214
-            $schema['properties'][$field_name . '_gmt']['description'] = sprintf(
215
-                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
216
-                wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
217
-            );
218
-        }
219
-        return $schema;
220
-    }
221
-
222
-
223
-
224
-    /**
225
-     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
226
-     *
227
-     * @return string
228
-     */
229
-    protected function getRouteFromRequest()
230
-    {
231
-        if (isset($GLOBALS['wp'])
232
-            && $GLOBALS['wp'] instanceof \WP
233
-            && isset($GLOBALS['wp']->query_vars['rest_route'])
234
-        ) {
235
-            return $GLOBALS['wp']->query_vars['rest_route'];
236
-        } else {
237
-            return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
238
-        }
239
-    }
240
-
241
-
242
-
243
-    /**
244
-     * Gets a single entity related to the model indicated in the path and its id
245
-
246
-     *
66
+	 * @param string           $version
67
+	 * @param string           $model_name
68
+	 * @return \WP_REST_Response|WP_Error
69
+	 */
70
+	public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
71
+	{
72
+		$controller = new Read();
73
+		try {
74
+			$controller->setRequestedVersion($version);
75
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
76
+				return $controller->sendResponse(
77
+					new WP_Error(
78
+						'endpoint_parsing_error',
79
+						sprintf(
80
+							__(
81
+								'There is no model for endpoint %s. Please contact event espresso support',
82
+								'event_espresso'
83
+							),
84
+							$model_name
85
+						)
86
+					)
87
+				);
88
+			}
89
+			return $controller->sendResponse(
90
+				$controller->getEntitiesFromModel(
91
+					$controller->getModelVersionInfo()->loadModel($model_name),
92
+					$request
93
+				)
94
+			);
95
+		} catch (Exception $e) {
96
+			return $controller->sendResponse($e);
97
+		}
98
+	}
99
+
100
+
101
+
102
+	/**
103
+	 * Prepares and returns schema for any OPTIONS request.
104
+	 *
105
+	 * @param string $version    The API endpoint version being used.
106
+	 * @param string $model_name Something like `Event` or `Registration`
107
+	 * @return array
108
+	 */
109
+	public static function handleSchemaRequest($version, $model_name)
110
+	{
111
+		$controller = new Read();
112
+		try {
113
+			$controller->setRequestedVersion($version);
114
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
115
+				return array();
116
+			}
117
+			//get the model for this version
118
+			$model = $controller->getModelVersionInfo()->loadModel($model_name);
119
+			$model_schema = new JsonModelSchema($model);
120
+			return $model_schema->getModelSchemaForRelations(
121
+				$controller->getModelVersionInfo()->relationSettings($model),
122
+				$controller->customizeSchemaForRestResponse(
123
+					$model,
124
+					$model_schema->getModelSchemaForFields(
125
+						$controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
126
+						$model_schema->getInitialSchemaStructure()
127
+					)
128
+				)
129
+			);
130
+		} catch (Exception $e) {
131
+			return array();
132
+		}
133
+	}
134
+
135
+
136
+
137
+	/**
138
+	 * This loops through each field in the given schema for the model and does the following:
139
+	 * - add any extra fields that are REST API specific and related to existing fields.
140
+	 * - transform default values into the correct format for a REST API response.
141
+	 *
142
+	 * @param EEM_Base $model
143
+	 * @param array     $schema
144
+	 * @return array  The final schema.
145
+	 */
146
+	protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
147
+	{
148
+		foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
149
+			$schema = $this->translateDefaultsForRestResponse(
150
+				$field_name,
151
+				$field,
152
+				$this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
153
+			);
154
+		}
155
+		return $schema;
156
+	}
157
+
158
+
159
+
160
+	/**
161
+	 * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
162
+	 * response.
163
+	 *
164
+	 * @param                      $field_name
165
+	 * @param EE_Model_Field_Base $field
166
+	 * @param array                $schema
167
+	 * @return array
168
+	 * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
169
+	 * did, let's know about it ASAP, so let the exception bubble up)
170
+	 */
171
+	protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
172
+	{
173
+		if (isset($schema['properties'][$field_name]['default'])) {
174
+			if (is_array($schema['properties'][$field_name]['default'])) {
175
+				foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
176
+					if ($default_key === 'raw') {
177
+						$schema['properties'][$field_name]['default'][$default_key] =
178
+							ModelDataTranslator::prepareFieldValueForJson(
179
+								$field,
180
+								$default_value,
181
+								$this->getModelVersionInfo()->requestedVersion()
182
+							);
183
+					}
184
+				}
185
+			} else {
186
+				$schema['properties'][$field_name]['default'] = ModelDataTranslator::prepareFieldValueForJson(
187
+					$field,
188
+					$schema['properties'][$field_name]['default'],
189
+					$this->getModelVersionInfo()->requestedVersion()
190
+				);
191
+			}
192
+		}
193
+		return $schema;
194
+	}
195
+
196
+
197
+
198
+	/**
199
+	 * Adds additional fields to the schema
200
+	 * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
201
+	 * needs to be added to the schema.
202
+	 *
203
+	 * @param                      $field_name
204
+	 * @param EE_Model_Field_Base $field
205
+	 * @param array                $schema
206
+	 * @return array
207
+	 */
208
+	protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
209
+	{
210
+		if ($field instanceof EE_Datetime_Field) {
211
+			$schema['properties'][$field_name . '_gmt'] = $field->getSchema();
212
+			//modify the description
213
+			$schema['properties'][$field_name . '_gmt']['description'] = sprintf(
214
+				esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
215
+				wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
216
+			);
217
+		}
218
+		return $schema;
219
+	}
220
+
221
+
222
+
223
+	/**
224
+	 * Used to figure out the route from the request when a `WP_REST_Request` object is not available
225
+	 *
226
+	 * @return string
227
+	 */
228
+	protected function getRouteFromRequest()
229
+	{
230
+		if (isset($GLOBALS['wp'])
231
+			&& $GLOBALS['wp'] instanceof \WP
232
+			&& isset($GLOBALS['wp']->query_vars['rest_route'])
233
+		) {
234
+			return $GLOBALS['wp']->query_vars['rest_route'];
235
+		} else {
236
+			return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
237
+		}
238
+	}
239
+
240
+
241
+
242
+	/**
243
+	 * Gets a single entity related to the model indicated in the path and its id
244
+	 *
247 245
 *@param WP_REST_Request $request
248
-     * @param string           $version
249
-     * @param string           $model_name
250
-     * @return \WP_REST_Response|WP_Error
251
-     */
252
-    public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
253
-    {
254
-        $controller = new Read();
255
-        try {
256
-            $controller->setRequestedVersion($version);
257
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
258
-                return $controller->sendResponse(
259
-                    new WP_Error(
260
-                        'endpoint_parsing_error',
261
-                        sprintf(
262
-                            __(
263
-                                'There is no model for endpoint %s. Please contact event espresso support',
264
-                                'event_espresso'
265
-                            ),
266
-                            $model_name
267
-                        )
268
-                    )
269
-                );
270
-            }
271
-            return $controller->sendResponse(
272
-                $controller->getEntityFromModel(
273
-                    $controller->getModelVersionInfo()->loadModel($model_name),
274
-                    $request
275
-                )
276
-            );
277
-        } catch (Exception $e) {
278
-            return $controller->sendResponse($e);
279
-        }
280
-    }
281
-
282
-
283
-
284
-    /**
285
-     * Gets all the related entities (or if its a belongs-to relation just the one)
286
-     * to the item with the given id
287
-
288
-     *
246
+	 * @param string           $version
247
+	 * @param string           $model_name
248
+	 * @return \WP_REST_Response|WP_Error
249
+	 */
250
+	public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
251
+	{
252
+		$controller = new Read();
253
+		try {
254
+			$controller->setRequestedVersion($version);
255
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
256
+				return $controller->sendResponse(
257
+					new WP_Error(
258
+						'endpoint_parsing_error',
259
+						sprintf(
260
+							__(
261
+								'There is no model for endpoint %s. Please contact event espresso support',
262
+								'event_espresso'
263
+							),
264
+							$model_name
265
+						)
266
+					)
267
+				);
268
+			}
269
+			return $controller->sendResponse(
270
+				$controller->getEntityFromModel(
271
+					$controller->getModelVersionInfo()->loadModel($model_name),
272
+					$request
273
+				)
274
+			);
275
+		} catch (Exception $e) {
276
+			return $controller->sendResponse($e);
277
+		}
278
+	}
279
+
280
+
281
+
282
+	/**
283
+	 * Gets all the related entities (or if its a belongs-to relation just the one)
284
+	 * to the item with the given id
285
+	 *
289 286
 *@param WP_REST_Request $request
290
-     * @param string           $version
291
-     * @param string           $model_name
292
-     * @param string           $related_model_name
293
-     * @return \WP_REST_Response|WP_Error
294
-     */
295
-    public static function handleRequestGetRelated(
296
-        WP_REST_Request $request,
297
-        $version,
298
-        $model_name,
299
-        $related_model_name
300
-    ) {
301
-        $controller = new Read();
302
-        try {
303
-            $controller->setRequestedVersion($version);
304
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
305
-                return $controller->sendResponse(
306
-                    new WP_Error(
307
-                        'endpoint_parsing_error',
308
-                        sprintf(
309
-                            __(
310
-                                'There is no model for endpoint %s. Please contact event espresso support',
311
-                                'event_espresso'
312
-                            ),
313
-                            $model_name
314
-                        )
315
-                    )
316
-                );
317
-            }
318
-            $main_model = $controller->getModelVersionInfo()->loadModel($model_name);
319
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
320
-                return $controller->sendResponse(
321
-                    new WP_Error(
322
-                        'endpoint_parsing_error',
323
-                        sprintf(
324
-                            __(
325
-                                'There is no model for endpoint %s. Please contact event espresso support',
326
-                                'event_espresso'
327
-                            ),
328
-                            $related_model_name
329
-                        )
330
-                    )
331
-                );
332
-            }
333
-            return $controller->sendResponse(
334
-                $controller->getEntitiesFromRelation(
335
-                    $request->get_param('id'),
336
-                    $main_model->related_settings_for($related_model_name),
337
-                    $request
338
-                )
339
-            );
340
-        } catch (Exception $e) {
341
-            return $controller->sendResponse($e);
342
-        }
343
-    }
344
-
345
-
346
-
347
-    /**
348
-     * Gets a collection for the given model and filters
349
-
350
-     *
287
+	 * @param string           $version
288
+	 * @param string           $model_name
289
+	 * @param string           $related_model_name
290
+	 * @return \WP_REST_Response|WP_Error
291
+	 */
292
+	public static function handleRequestGetRelated(
293
+		WP_REST_Request $request,
294
+		$version,
295
+		$model_name,
296
+		$related_model_name
297
+	) {
298
+		$controller = new Read();
299
+		try {
300
+			$controller->setRequestedVersion($version);
301
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
302
+				return $controller->sendResponse(
303
+					new WP_Error(
304
+						'endpoint_parsing_error',
305
+						sprintf(
306
+							__(
307
+								'There is no model for endpoint %s. Please contact event espresso support',
308
+								'event_espresso'
309
+							),
310
+							$model_name
311
+						)
312
+					)
313
+				);
314
+			}
315
+			$main_model = $controller->getModelVersionInfo()->loadModel($model_name);
316
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
317
+				return $controller->sendResponse(
318
+					new WP_Error(
319
+						'endpoint_parsing_error',
320
+						sprintf(
321
+							__(
322
+								'There is no model for endpoint %s. Please contact event espresso support',
323
+								'event_espresso'
324
+							),
325
+							$related_model_name
326
+						)
327
+					)
328
+				);
329
+			}
330
+			return $controller->sendResponse(
331
+				$controller->getEntitiesFromRelation(
332
+					$request->get_param('id'),
333
+					$main_model->related_settings_for($related_model_name),
334
+					$request
335
+				)
336
+			);
337
+		} catch (Exception $e) {
338
+			return $controller->sendResponse($e);
339
+		}
340
+	}
341
+
342
+
343
+
344
+	/**
345
+	 * Gets a collection for the given model and filters
346
+	 *
351 347
 *@param EEM_Base        $model
352
-     * @param WP_REST_Request $request
353
-     * @return array|WP_Error
354
-     */
355
-    public function getEntitiesFromModel($model, $request)
356
-    {
357
-        $query_params = $this->createModelQueryParams($model, $request->get_params());
358
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
359
-            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
360
-            return new WP_Error(
361
-                sprintf('rest_%s_cannot_list', $model_name_plural),
362
-                sprintf(
363
-                    __('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
364
-                    $model_name_plural,
365
-                    Capabilities::getMissingPermissionsString($model, $query_params['caps'])
366
-                ),
367
-                array('status' => 403)
368
-            );
369
-        }
370
-        if (! $request->get_header('no_rest_headers')) {
371
-            $this->setHeadersFromQueryParams($model, $query_params);
372
-        }
373
-        /** @type array $results */
374
-        $results = $model->get_all_wpdb_results($query_params);
375
-        $nice_results = array();
376
-        foreach ($results as $result) {
377
-            $nice_results[] = $this->createEntityFromWpdbResult(
378
-                $model,
379
-                $result,
380
-                $request
381
-            );
382
-        }
383
-        return $nice_results;
384
-    }
385
-
386
-
387
-
388
-    /**
389
-     * Gets the collection for given relation object
390
-     * The same as Read::get_entities_from_model(), except if the relation
391
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
392
-     * the join-model-object into the results
393
-     *
394
-     * @param array                   $primary_model_query_params query params for finding the item from which
395
-     *                                                            relations will be based
396
-     * @param \EE_Model_Relation_Base $relation
397
-     * @param WP_REST_Request        $request
398
-     * @return WP_Error|array
399
-     * @throws RestException
400
-     */
401
-    protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
402
-    {
403
-        $context = $this->validateContext($request->get_param('caps'));
404
-        $model = $relation->get_this_model();
405
-        $related_model = $relation->get_other_model();
406
-        if (! isset($primary_model_query_params[0])) {
407
-            $primary_model_query_params[0] = array();
408
-        }
409
-        //check if they can access the 1st model object
410
-        $primary_model_query_params = array(
411
-            0       => $primary_model_query_params[0],
412
-            'limit' => 1,
413
-        );
414
-        if ($model instanceof \EEM_Soft_Delete_Base) {
415
-            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
416
-                $primary_model_query_params
417
-            );
418
-        }
419
-        $restricted_query_params = $primary_model_query_params;
420
-        $restricted_query_params['caps'] = $context;
421
-        $this->setDebugInfo('main model query params', $restricted_query_params);
422
-        $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
423
-        if (! (
424
-            Capabilities::currentUserHasPartialAccessTo($related_model, $context)
425
-            && $model->exists($restricted_query_params)
426
-        )
427
-        ) {
428
-            if ($relation instanceof EE_Belongs_To_Relation) {
429
-                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
430
-            } else {
431
-                $related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
432
-                    $related_model->get_this_model_name()
433
-                );
434
-            }
435
-            return new WP_Error(
436
-                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
437
-                sprintf(
438
-                    __(
439
-                        'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
440
-                        'event_espresso'
441
-                    ),
442
-                    $related_model_name_maybe_plural,
443
-                    $relation->get_this_model()->get_this_model_name(),
444
-                    implode(
445
-                        ',',
446
-                        array_keys(
447
-                            Capabilities::getMissingPermissions($related_model, $context)
448
-                        )
449
-                    )
450
-                ),
451
-                array('status' => 403)
452
-            );
453
-        }
454
-        $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
455
-        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
456
-            $query_params[0][$relation->get_this_model()->get_this_model_name()
457
-                             . '.'
458
-                             . $where_condition_key] = $where_condition_value;
459
-        }
460
-        $query_params['default_where_conditions'] = 'none';
461
-        $query_params['caps'] = $context;
462
-        if (! $request->get_header('no_rest_headers')) {
463
-            $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
464
-        }
465
-        /** @type array $results */
466
-        $results = $relation->get_other_model()->get_all_wpdb_results($query_params);
467
-        $nice_results = array();
468
-        foreach ($results as $result) {
469
-            $nice_result = $this->createEntityFromWpdbResult(
470
-                $relation->get_other_model(),
471
-                $result,
472
-                $request
473
-            );
474
-            if ($relation instanceof \EE_HABTM_Relation) {
475
-                //put the unusual stuff (properties from the HABTM relation) first, and make sure
476
-                //if there are conflicts we prefer the properties from the main model
477
-                $join_model_result = $this->createEntityFromWpdbResult(
478
-                    $relation->get_join_model(),
479
-                    $result,
480
-                    $request
481
-                );
482
-                $joined_result = array_merge($nice_result, $join_model_result);
483
-                //but keep the meta stuff from the main model
484
-                if (isset($nice_result['meta'])) {
485
-                    $joined_result['meta'] = $nice_result['meta'];
486
-                }
487
-                $nice_result = $joined_result;
488
-            }
489
-            $nice_results[] = $nice_result;
490
-        }
491
-        if ($relation instanceof EE_Belongs_To_Relation) {
492
-            return array_shift($nice_results);
493
-        } else {
494
-            return $nice_results;
495
-        }
496
-    }
497
-
498
-
499
-
500
-    /**
501
-     * Gets the collection for given relation object
502
-     * The same as Read::get_entities_from_model(), except if the relation
503
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
504
-     * the join-model-object into the results
505
-
506
-     *
348
+	 * @param WP_REST_Request $request
349
+	 * @return array|WP_Error
350
+	 */
351
+	public function getEntitiesFromModel($model, $request)
352
+	{
353
+		$query_params = $this->createModelQueryParams($model, $request->get_params());
354
+		if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
355
+			$model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
356
+			return new WP_Error(
357
+				sprintf('rest_%s_cannot_list', $model_name_plural),
358
+				sprintf(
359
+					__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
360
+					$model_name_plural,
361
+					Capabilities::getMissingPermissionsString($model, $query_params['caps'])
362
+				),
363
+				array('status' => 403)
364
+			);
365
+		}
366
+		if (! $request->get_header('no_rest_headers')) {
367
+			$this->setHeadersFromQueryParams($model, $query_params);
368
+		}
369
+		/** @type array $results */
370
+		$results = $model->get_all_wpdb_results($query_params);
371
+		$nice_results = array();
372
+		foreach ($results as $result) {
373
+			$nice_results[] = $this->createEntityFromWpdbResult(
374
+				$model,
375
+				$result,
376
+				$request
377
+			);
378
+		}
379
+		return $nice_results;
380
+	}
381
+
382
+
383
+
384
+	/**
385
+	 * Gets the collection for given relation object
386
+	 * The same as Read::get_entities_from_model(), except if the relation
387
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
388
+	 * the join-model-object into the results
389
+	 *
390
+	 * @param array                   $primary_model_query_params query params for finding the item from which
391
+	 *                                                            relations will be based
392
+	 * @param \EE_Model_Relation_Base $relation
393
+	 * @param WP_REST_Request        $request
394
+	 * @return WP_Error|array
395
+	 * @throws RestException
396
+	 */
397
+	protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
398
+	{
399
+		$context = $this->validateContext($request->get_param('caps'));
400
+		$model = $relation->get_this_model();
401
+		$related_model = $relation->get_other_model();
402
+		if (! isset($primary_model_query_params[0])) {
403
+			$primary_model_query_params[0] = array();
404
+		}
405
+		//check if they can access the 1st model object
406
+		$primary_model_query_params = array(
407
+			0       => $primary_model_query_params[0],
408
+			'limit' => 1,
409
+		);
410
+		if ($model instanceof \EEM_Soft_Delete_Base) {
411
+			$primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
412
+				$primary_model_query_params
413
+			);
414
+		}
415
+		$restricted_query_params = $primary_model_query_params;
416
+		$restricted_query_params['caps'] = $context;
417
+		$this->setDebugInfo('main model query params', $restricted_query_params);
418
+		$this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
419
+		if (! (
420
+			Capabilities::currentUserHasPartialAccessTo($related_model, $context)
421
+			&& $model->exists($restricted_query_params)
422
+		)
423
+		) {
424
+			if ($relation instanceof EE_Belongs_To_Relation) {
425
+				$related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
426
+			} else {
427
+				$related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
428
+					$related_model->get_this_model_name()
429
+				);
430
+			}
431
+			return new WP_Error(
432
+				sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
433
+				sprintf(
434
+					__(
435
+						'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
436
+						'event_espresso'
437
+					),
438
+					$related_model_name_maybe_plural,
439
+					$relation->get_this_model()->get_this_model_name(),
440
+					implode(
441
+						',',
442
+						array_keys(
443
+							Capabilities::getMissingPermissions($related_model, $context)
444
+						)
445
+					)
446
+				),
447
+				array('status' => 403)
448
+			);
449
+		}
450
+		$query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
451
+		foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
452
+			$query_params[0][$relation->get_this_model()->get_this_model_name()
453
+							 . '.'
454
+							 . $where_condition_key] = $where_condition_value;
455
+		}
456
+		$query_params['default_where_conditions'] = 'none';
457
+		$query_params['caps'] = $context;
458
+		if (! $request->get_header('no_rest_headers')) {
459
+			$this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
460
+		}
461
+		/** @type array $results */
462
+		$results = $relation->get_other_model()->get_all_wpdb_results($query_params);
463
+		$nice_results = array();
464
+		foreach ($results as $result) {
465
+			$nice_result = $this->createEntityFromWpdbResult(
466
+				$relation->get_other_model(),
467
+				$result,
468
+				$request
469
+			);
470
+			if ($relation instanceof \EE_HABTM_Relation) {
471
+				//put the unusual stuff (properties from the HABTM relation) first, and make sure
472
+				//if there are conflicts we prefer the properties from the main model
473
+				$join_model_result = $this->createEntityFromWpdbResult(
474
+					$relation->get_join_model(),
475
+					$result,
476
+					$request
477
+				);
478
+				$joined_result = array_merge($nice_result, $join_model_result);
479
+				//but keep the meta stuff from the main model
480
+				if (isset($nice_result['meta'])) {
481
+					$joined_result['meta'] = $nice_result['meta'];
482
+				}
483
+				$nice_result = $joined_result;
484
+			}
485
+			$nice_results[] = $nice_result;
486
+		}
487
+		if ($relation instanceof EE_Belongs_To_Relation) {
488
+			return array_shift($nice_results);
489
+		} else {
490
+			return $nice_results;
491
+		}
492
+	}
493
+
494
+
495
+
496
+	/**
497
+	 * Gets the collection for given relation object
498
+	 * The same as Read::get_entities_from_model(), except if the relation
499
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
500
+	 * the join-model-object into the results
501
+	 *
507 502
 *@param string                  $id the ID of the thing we are fetching related stuff from
508
-     * @param \EE_Model_Relation_Base $relation
509
-     * @param WP_REST_Request        $request
510
-     * @return array|WP_Error
511
-     * @throws EE_Error
512
-     */
513
-    public function getEntitiesFromRelation($id, $relation, $request)
514
-    {
515
-        if (! $relation->get_this_model()->has_primary_key_field()) {
516
-            throw new EE_Error(
517
-                sprintf(
518
-                    __(
519
-                        // @codingStandardsIgnoreStart
520
-                        'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
521
-                        // @codingStandardsIgnoreEnd
522
-                        'event_espresso'
523
-                    ),
524
-                    $relation->get_this_model()->get_this_model_name()
525
-                )
526
-            );
527
-        }
528
-        return $this->getEntitiesFromRelationUsingModelQueryParams(
529
-            array(
530
-                array(
531
-                    $relation->get_this_model()->primary_key_name() => $id,
532
-                ),
533
-            ),
534
-            $relation,
535
-            $request
536
-        );
537
-    }
538
-
539
-
540
-
541
-    /**
542
-     * Sets the headers that are based on the model and query params,
543
-     * like the total records. This should only be called on the original request
544
-     * from the client, not on subsequent internal
545
-     *
546
-     * @param EEM_Base $model
547
-     * @param array     $query_params
548
-     * @return void
549
-     */
550
-    protected function setHeadersFromQueryParams($model, $query_params)
551
-    {
552
-        $this->setDebugInfo('model query params', $query_params);
553
-        $this->setDebugInfo(
554
-            'missing caps',
555
-            Capabilities::getMissingPermissionsString($model, $query_params['caps'])
556
-        );
557
-        //normally the limit to a 2-part array, where the 2nd item is the limit
558
-        if (! isset($query_params['limit'])) {
559
-            $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
560
-        }
561
-        if (is_array($query_params['limit'])) {
562
-            $limit_parts = $query_params['limit'];
563
-        } else {
564
-            $limit_parts = explode(',', $query_params['limit']);
565
-            if (count($limit_parts) == 1) {
566
-                $limit_parts = array(0, $limit_parts[0]);
567
-            }
568
-        }
569
-        //remove the group by and having parts of the query, as those will
570
-        //make the sql query return an array of values, instead of just a single value
571
-        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
572
-        $count = $model->count($query_params, null, true);
573
-        $pages = $count / $limit_parts[1];
574
-        $this->setResponseHeader('Total', $count, false);
575
-        $this->setResponseHeader('PageSize', $limit_parts[1], false);
576
-        $this->setResponseHeader('TotalPages', ceil($pages), false);
577
-    }
578
-
579
-
580
-
581
-    /**
582
-     * Changes database results into REST API entities
583
-     *
584
-     * @param EEM_Base        $model
585
-     * @param array            $db_row     like results from $wpdb->get_results()
586
-     * @param WP_REST_Request $rest_request
587
-     * @param string           $deprecated no longer used
588
-     * @return array ready for being converted into json for sending to client
589
-     */
590
-    public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
591
-    {
592
-        if (! $rest_request instanceof WP_REST_Request) {
593
-            //ok so this was called in the old style, where the 3rd arg was
594
-            //$include, and the 4th arg was $context
595
-            //now setup the request just to avoid fatal errors, although we won't be able
596
-            //to truly make use of it because it's kinda devoid of info
597
-            $rest_request = new WP_REST_Request();
598
-            $rest_request->set_param('include', $rest_request);
599
-            $rest_request->set_param('caps', $deprecated);
600
-        }
601
-        if ($rest_request->get_param('caps') == null) {
602
-            $rest_request->set_param('caps', EEM_Base::caps_read);
603
-        }
604
-        $entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
605
-        $entity_array = $this->addExtraFields($model, $db_row, $entity_array);
606
-        $entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
607
-        $entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request);
608
-        $entity_array = apply_filters(
609
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
610
-            $entity_array,
611
-            $model,
612
-            $rest_request->get_param('caps'),
613
-            $rest_request,
614
-            $this
615
-        );
616
-        $entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row);
617
-        $entity_array = apply_filters(
618
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
619
-            $entity_array,
620
-            $model,
621
-            $rest_request->get_param('caps'),
622
-            $rest_request,
623
-            $this
624
-        );
625
-        $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
626
-            $entity_array,
627
-            $model,
628
-            $rest_request->get_param('caps'),
629
-            $this->getModelVersionInfo(),
630
-            $model->get_index_primary_key_string(
631
-                $model->deduce_fields_n_values_from_cols_n_values($db_row)
632
-            )
633
-        );
634
-        $this->setDebugInfo(
635
-            'inaccessible fields',
636
-            array_keys(array_diff_key($entity_array, $result_without_inaccessible_fields))
637
-        );
638
-        return apply_filters(
639
-            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
640
-            $result_without_inaccessible_fields,
641
-            $model,
642
-            $rest_request->get_param('caps')
643
-        );
644
-    }
645
-
646
-
647
-
648
-    /**
649
-     * Creates a REST entity array (JSON object we're going to return in the response, but
650
-     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
651
-     * from $wpdb->get_row( $sql, ARRAY_A)
652
-     *
653
-     * @param EEM_Base $model
654
-     * @param array     $db_row
655
-     * @return array entity mostly ready for converting to JSON and sending in the response
656
-     *
657
-     */
658
-    protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
659
-    {
660
-        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
661
-        $result = array_intersect_key(
662
-            $result,
663
-            $this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
664
-        );
665
-        //if this is a CPT, we need to set the global $post to it,
666
-        //otherwise shortcodes etc won't work properly while rendering it
667
-        if ($model instanceof \EEM_CPT_Base) {
668
-            $do_chevy_shuffle = true;
669
-        } else {
670
-            $do_chevy_shuffle = false;
671
-        }
672
-        if ($do_chevy_shuffle) {
673
-            global $post;
674
-            $old_post = $post;
675
-            $post = get_post($result[$model->primary_key_name()]);
676
-            if (! $post instanceof \WP_Post) {
677
-                //well that's weird, because $result is what we JUST fetched from the database
678
-                throw new RestException(
679
-                    'error_fetching_post_from_database_results',
680
-                    esc_html__(
681
-                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
682
-                        'event_espresso'
683
-                    )
684
-                );
685
-            }
686
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
687
-            $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
688
-                $model_object_classname,
689
-                $result,
690
-                false,
691
-                false
692
-            );
693
-        }
694
-        foreach ($result as $field_name => $field_value) {
695
-            $field_obj = $model->field_settings_for($field_name);
696
-            if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
697
-                unset($result[$field_name]);
698
-            } elseif ($this->isSubclassOfOne(
699
-                $field_obj,
700
-                $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
701
-            )
702
-            ) {
703
-                $result[$field_name] = array(
704
-                    'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
705
-                    'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
706
-                );
707
-            } elseif ($this->isSubclassOfOne(
708
-                $field_obj,
709
-                $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
710
-            )
711
-            ) {
712
-                $result[$field_name] = array(
713
-                    'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
714
-                    'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
715
-                );
716
-            } elseif ($field_obj instanceof \EE_Datetime_Field) {
717
-                $field_value = $field_obj->prepare_for_set_from_db($field_value);
718
-                $timezone = $field_value->getTimezone();
719
-                EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
720
-                $result[$field_name . '_gmt'] = ModelDataTranslator::prepareFieldValuesForJson(
721
-                    $field_obj,
722
-                    $field_value,
723
-                    $this->getModelVersionInfo()->requestedVersion()
724
-                );
725
-                EEH_DTT_Helper::setTimezone($field_value, $timezone);
726
-                $result[$field_name] = ModelDataTranslator::prepareFieldValuesForJson(
727
-                    $field_obj,
728
-                    $field_value,
729
-                    $this->getModelVersionInfo()->requestedVersion()
730
-                );
731
-            } else {
732
-                $result[$field_name] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
733
-            }
734
-        }
735
-        if ($do_chevy_shuffle) {
736
-            $post = $old_post;
737
-        }
738
-        return $result;
739
-    }
740
-
741
-
742
-
743
-    /**
744
-     * Takes a value all the way from the DB representation, to the model object's representation, to the
745
-     * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
746
-     * representation using $field_obj->prepare_for_set_from_db())
747
-     *
748
-     * @param EE_Model_Field_Base $field_obj
749
-     * @param mixed $value as it's stored on a model object
750
-     * @param string $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
751
-     * @return mixed
752
-     * @throws ObjectDetectedException if $value contains a PHP object
753
-     */
754
-    protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
755
-    {
756
-        $value = $field_obj->prepare_for_set_from_db($value);
757
-        switch ($format) {
758
-            case 'pretty':
759
-                $value = $field_obj->prepare_for_pretty_echoing($value);
760
-                break;
761
-            case 'normal':
762
-            default:
763
-                $value = $field_obj->prepare_for_get($value);
764
-                break;
765
-        }
766
-        return ModelDataTranslator::prepareFieldValuesForJson(
767
-            $field_obj,
768
-            $value,
769
-            $this->getModelVersionInfo()->requestedVersion()
770
-        );
771
-    }
772
-
773
-
774
-
775
-    /**
776
-     * Adds a few extra fields to the entity response
777
-     *
778
-     * @param EEM_Base $model
779
-     * @param array     $db_row
780
-     * @param array     $entity_array
781
-     * @return array modified entity
782
-     */
783
-    protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
784
-    {
785
-        if ($model instanceof EEM_CPT_Base) {
786
-            $entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
787
-        }
788
-        return $entity_array;
789
-    }
790
-
791
-
792
-
793
-    /**
794
-     * Gets links we want to add to the response
795
-     *
796
-     * @global \WP_REST_Server $wp_rest_server
797
-     * @param EEM_Base        $model
798
-     * @param array            $db_row
799
-     * @param array            $entity_array
800
-     * @return array the _links item in the entity
801
-     */
802
-    protected function getEntityLinks($model, $db_row, $entity_array)
803
-    {
804
-        //add basic links
805
-        $links = array();
806
-        if ($model->has_primary_key_field()) {
807
-            $links['self'] = array(
808
-                array(
809
-                    'href' => $this->getVersionedLinkTo(
810
-                        EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
811
-                        . '/'
812
-                        . $entity_array[$model->primary_key_name()]
813
-                    ),
814
-                ),
815
-            );
816
-        }
817
-        $links['collection'] = array(
818
-            array(
819
-                'href' => $this->getVersionedLinkTo(
820
-                    EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
821
-                ),
822
-            ),
823
-        );
824
-        //add links to related models
825
-        if ($model->has_primary_key_field()) {
826
-            foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
827
-                $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
828
-                $links[EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part] = array(
829
-                    array(
830
-                        'href'   => $this->getVersionedLinkTo(
831
-                            EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
832
-                            . '/'
833
-                            . $entity_array[$model->primary_key_name()]
834
-                            . '/'
835
-                            . $related_model_part
836
-                        ),
837
-                        'single' => $relation_obj instanceof EE_Belongs_To_Relation ? true : false,
838
-                    ),
839
-                );
840
-            }
841
-        }
842
-        return $links;
843
-    }
844
-
845
-
846
-
847
-    /**
848
-     * Adds the included models indicated in the request to the entity provided
849
-     *
850
-     * @param EEM_Base        $model
851
-     * @param WP_REST_Request $rest_request
852
-     * @param array            $entity_array
853
-     * @param array            $db_row
854
-     * @return array the modified entity
855
-     */
856
-    protected function includeRequestedModels(
857
-        EEM_Base $model,
858
-        WP_REST_Request $rest_request,
859
-        $entity_array,
860
-        $db_row = array()
861
-    ) {
862
-        //if $db_row not included, hope the entity array has what we need
863
-        if (! $db_row) {
864
-            $db_row = $entity_array;
865
-        }
866
-        $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
867
-        $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
868
-        //if they passed in * or didn't specify any includes, return everything
869
-        if (! in_array('*', $includes_for_this_model)
870
-            && ! empty($includes_for_this_model)
871
-        ) {
872
-            if ($model->has_primary_key_field()) {
873
-                //always include the primary key. ya just gotta know that at least
874
-                $includes_for_this_model[] = $model->primary_key_name();
875
-            }
876
-            if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
877
-                $includes_for_this_model[] = '_calculated_fields';
878
-            }
879
-            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
880
-        }
881
-        $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
882
-        foreach ($relation_settings as $relation_name => $relation_obj) {
883
-            $related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
884
-                $rest_request->get_param('include'),
885
-                $relation_name
886
-            );
887
-            $related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
888
-                $rest_request->get_param('calculate'),
889
-                $relation_name
890
-            );
891
-            //did they specify they wanted to include a related model, or
892
-            //specific fields from a related model?
893
-            //or did they specify to calculate a field from a related model?
894
-            if ($related_fields_to_include || $related_fields_to_calculate) {
895
-                //if so, we should include at least some part of the related model
896
-                $pretend_related_request = new WP_REST_Request();
897
-                $pretend_related_request->set_query_params(
898
-                    array(
899
-                        'caps'      => $rest_request->get_param('caps'),
900
-                        'include'   => $related_fields_to_include,
901
-                        'calculate' => $related_fields_to_calculate,
902
-                    )
903
-                );
904
-                $pretend_related_request->add_header('no_rest_headers', true);
905
-                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
906
-                    $model->get_index_primary_key_string(
907
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
908
-                    )
909
-                );
910
-                $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
911
-                    $primary_model_query_params,
912
-                    $relation_obj,
913
-                    $pretend_related_request
914
-                );
915
-                $entity_array[Read::getRelatedEntityName($relation_name, $relation_obj)] = $related_results
916
-                                                                                           instanceof
917
-                                                                                           WP_Error
918
-                    ? null
919
-                    : $related_results;
920
-            }
921
-        }
922
-        return $entity_array;
923
-    }
924
-
925
-
926
-
927
-    /**
928
-     * Returns a new array with all the names of models removed. Eg
929
-     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
930
-     *
931
-     * @param array $arr
932
-     * @return array
933
-     */
934
-    private function removeModelNamesFromArray($arr)
935
-    {
936
-        return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
937
-    }
938
-
939
-
940
-
941
-    /**
942
-     * Gets the calculated fields for the response
943
-     *
944
-     * @param EEM_Base        $model
945
-     * @param array            $wpdb_row
946
-     * @param WP_REST_Request $rest_request
947
-     * @return \stdClass the _calculations item in the entity
948
-     * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
949
-     * did, let's know about it ASAP, so let the exception bubble up)
950
-     */
951
-    protected function getEntityCalculations($model, $wpdb_row, $rest_request)
952
-    {
953
-        $calculated_fields = $this->explodeAndGetItemsPrefixedWith(
954
-            $rest_request->get_param('calculate'),
955
-            ''
956
-        );
957
-        //note: setting calculate=* doesn't do anything
958
-        $calculated_fields_to_return = new \stdClass();
959
-        foreach ($calculated_fields as $field_to_calculate) {
960
-            try {
961
-                $calculated_fields_to_return->$field_to_calculate = ModelDataTranslator::prepareFieldValueForJson(
962
-                    null,
963
-                    $this->fields_calculator->retrieveCalculatedFieldValue(
964
-                        $model,
965
-                        $field_to_calculate,
966
-                        $wpdb_row,
967
-                        $rest_request,
968
-                        $this
969
-                    ),
970
-                    $this->getModelVersionInfo()->requestedVersion()
971
-                );
972
-            } catch (RestException $e) {
973
-                //if we don't have permission to read it, just leave it out. but let devs know about the problem
974
-                $this->setResponseHeader(
975
-                    'Notices-Field-Calculation-Errors['
976
-                    . $e->getStringCode()
977
-                    . ']['
978
-                    . $model->get_this_model_name()
979
-                    . ']['
980
-                    . $field_to_calculate
981
-                    . ']',
982
-                    $e->getMessage(),
983
-                    true
984
-                );
985
-            }
986
-        }
987
-        return $calculated_fields_to_return;
988
-    }
989
-
990
-
991
-
992
-    /**
993
-     * Gets the full URL to the resource, taking the requested version into account
994
-     *
995
-     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
996
-     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
997
-     */
998
-    public function getVersionedLinkTo($link_part_after_version_and_slash)
999
-    {
1000
-        return rest_url(
1001
-            EED_Core_Rest_Api::get_versioned_route_to(
1002
-                $link_part_after_version_and_slash,
1003
-                $this->getModelVersionInfo()->requestedVersion()
1004
-            )
1005
-        );
1006
-    }
1007
-
1008
-
1009
-
1010
-    /**
1011
-     * Gets the correct lowercase name for the relation in the API according
1012
-     * to the relation's type
1013
-     *
1014
-     * @param string                  $relation_name
1015
-     * @param \EE_Model_Relation_Base $relation_obj
1016
-     * @return string
1017
-     */
1018
-    public static function getRelatedEntityName($relation_name, $relation_obj)
1019
-    {
1020
-        if ($relation_obj instanceof EE_Belongs_To_Relation) {
1021
-            return strtolower($relation_name);
1022
-        } else {
1023
-            return EEH_Inflector::pluralize_and_lower($relation_name);
1024
-        }
1025
-    }
1026
-
1027
-
1028
-
1029
-    /**
1030
-     * Gets the one model object with the specified id for the specified model
1031
-     *
1032
-     * @param EEM_Base        $model
1033
-     * @param WP_REST_Request $request
1034
-     * @return array|WP_Error
1035
-     */
1036
-    public function getEntityFromModel($model, $request)
1037
-    {
1038
-        $context = $this->validateContext($request->get_param('caps'));
1039
-        return $this->getOneOrReportPermissionError($model, $request, $context);
1040
-    }
1041
-
1042
-
1043
-
1044
-    /**
1045
-     * If a context is provided which isn't valid, maybe it was added in a future
1046
-     * version so just treat it as a default read
1047
-     *
1048
-     * @param string $context
1049
-     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1050
-     */
1051
-    public function validateContext($context)
1052
-    {
1053
-        if (! $context) {
1054
-            $context = EEM_Base::caps_read;
1055
-        }
1056
-        $valid_contexts = EEM_Base::valid_cap_contexts();
1057
-        if (in_array($context, $valid_contexts)) {
1058
-            return $context;
1059
-        } else {
1060
-            return EEM_Base::caps_read;
1061
-        }
1062
-    }
1063
-
1064
-
1065
-
1066
-    /**
1067
-     * Verifies the passed in value is an allowable default where conditions value.
1068
-     *
1069
-     * @param $default_query_params
1070
-     * @return string
1071
-     */
1072
-    public function validateDefaultQueryParams($default_query_params)
1073
-    {
1074
-        $valid_default_where_conditions_for_api_calls = array(
1075
-            EEM_Base::default_where_conditions_all,
1076
-            EEM_Base::default_where_conditions_minimum_all,
1077
-            EEM_Base::default_where_conditions_minimum_others,
1078
-        );
1079
-        if (! $default_query_params) {
1080
-            $default_query_params = EEM_Base::default_where_conditions_all;
1081
-        }
1082
-        if (in_array(
1083
-            $default_query_params,
1084
-            $valid_default_where_conditions_for_api_calls,
1085
-            true
1086
-        )) {
1087
-            return $default_query_params;
1088
-        } else {
1089
-            return EEM_Base::default_where_conditions_all;
1090
-        }
1091
-    }
1092
-
1093
-
1094
-
1095
-    /**
1096
-     * Translates API filter get parameter into $query_params array used by EEM_Base::get_all().
1097
-     * Note: right now the query parameter keys for fields (and related fields)
1098
-     * can be left as-is, but it's quite possible this will change someday.
1099
-     * Also, this method's contents might be candidate for moving to Model_Data_Translator
1100
-     *
1101
-     * @param EEM_Base $model
1102
-     * @param array     $query_parameters from $_GET parameter @see Read:handle_request_get_all
1103
-     * @return array like what EEM_Base::get_all() expects or FALSE to indicate
1104
-     *                                    that absolutely no results should be returned
1105
-     * @throws EE_Error
1106
-     * @throws RestException
1107
-     */
1108
-    public function createModelQueryParams($model, $query_parameters)
1109
-    {
1110
-        $model_query_params = array();
1111
-        if (isset($query_parameters['where'])) {
1112
-            $model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1113
-                $query_parameters['where'],
1114
-                $model,
1115
-                $this->getModelVersionInfo()->requestedVersion()
1116
-            );
1117
-        }
1118
-        if (isset($query_parameters['order_by'])) {
1119
-            $order_by = $query_parameters['order_by'];
1120
-        } elseif (isset($query_parameters['orderby'])) {
1121
-            $order_by = $query_parameters['orderby'];
1122
-        } else {
1123
-            $order_by = null;
1124
-        }
1125
-        if ($order_by !== null) {
1126
-            if (is_array($order_by)) {
1127
-                $order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1128
-            } else {
1129
-                //it's a single item
1130
-                $order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1131
-            }
1132
-            $model_query_params['order_by'] = $order_by;
1133
-        }
1134
-        if (isset($query_parameters['group_by'])) {
1135
-            $group_by = $query_parameters['group_by'];
1136
-        } elseif (isset($query_parameters['groupby'])) {
1137
-            $group_by = $query_parameters['groupby'];
1138
-        } else {
1139
-            $group_by = array_keys($model->get_combined_primary_key_fields());
1140
-        }
1141
-        //make sure they're all real names
1142
-        if (is_array($group_by)) {
1143
-            $group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1144
-        }
1145
-        if ($group_by !== null) {
1146
-            $model_query_params['group_by'] = $group_by;
1147
-        }
1148
-        if (isset($query_parameters['having'])) {
1149
-            $model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1150
-                $query_parameters['having'],
1151
-                $model,
1152
-                $this->getModelVersionInfo()->requestedVersion()
1153
-            );
1154
-        }
1155
-        if (isset($query_parameters['order'])) {
1156
-            $model_query_params['order'] = $query_parameters['order'];
1157
-        }
1158
-        if (isset($query_parameters['mine'])) {
1159
-            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1160
-        }
1161
-        if (isset($query_parameters['limit'])) {
1162
-            //limit should be either a string like '23' or '23,43', or an array with two items in it
1163
-            if (! is_array($query_parameters['limit'])) {
1164
-                $limit_array = explode(',', (string)$query_parameters['limit']);
1165
-            } else {
1166
-                $limit_array = $query_parameters['limit'];
1167
-            }
1168
-            $sanitized_limit = array();
1169
-            foreach ($limit_array as $key => $limit_part) {
1170
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1171
-                    throw new EE_Error(
1172
-                        sprintf(
1173
-                            __(
1174
-                                // @codingStandardsIgnoreStart
1175
-                                'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1176
-                                // @codingStandardsIgnoreEnd
1177
-                                'event_espresso'
1178
-                            ),
1179
-                            wp_json_encode($query_parameters['limit'])
1180
-                        )
1181
-                    );
1182
-                }
1183
-                $sanitized_limit[] = (int)$limit_part;
1184
-            }
1185
-            $model_query_params['limit'] = implode(',', $sanitized_limit);
1186
-        } else {
1187
-            $model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1188
-        }
1189
-        if (isset($query_parameters['caps'])) {
1190
-            $model_query_params['caps'] = $this->validateContext($query_parameters['caps']);
1191
-        } else {
1192
-            $model_query_params['caps'] = EEM_Base::caps_read;
1193
-        }
1194
-        if (isset($query_parameters['default_where_conditions'])) {
1195
-            $model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1196
-                $query_parameters['default_where_conditions']
1197
-            );
1198
-        }
1199
-        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_parameters, $model);
1200
-    }
1201
-
1202
-
1203
-
1204
-    /**
1205
-     * Changes the REST-style query params for use in the models
1206
-     *
1207
-     * @deprecated
1208
-     * @param EEM_Base $model
1209
-     * @param array     $query_params sub-array from @see EEM_Base::get_all()
1210
-     * @return array
1211
-     */
1212
-    public function prepareRestQueryParamsKeyForModels($model, $query_params)
1213
-    {
1214
-        $model_ready_query_params = array();
1215
-        foreach ($query_params as $key => $value) {
1216
-            if (is_array($value)) {
1217
-                $model_ready_query_params[$key] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1218
-            } else {
1219
-                $model_ready_query_params[$key] = $value;
1220
-            }
1221
-        }
1222
-        return $model_ready_query_params;
1223
-    }
1224
-
1225
-
1226
-
1227
-    /**
1228
-     * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1229
-     * @param $model
1230
-     * @param $query_params
1231
-     * @return array
1232
-     */
1233
-    public function prepareRestQueryParamsValuesForModels($model, $query_params)
1234
-    {
1235
-        $model_ready_query_params = array();
1236
-        foreach ($query_params as $key => $value) {
1237
-            if (is_array($value)) {
1238
-                $model_ready_query_params[$key] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1239
-            } else {
1240
-                $model_ready_query_params[$key] = $value;
1241
-            }
1242
-        }
1243
-        return $model_ready_query_params;
1244
-    }
1245
-
1246
-
1247
-
1248
-    /**
1249
-     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1250
-     * If no prefix is specified, returns items with no period.
1251
-     *
1252
-     * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1253
-     * @param string       $prefix            "Event" or "foobar"
1254
-     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1255
-     *                                        we only return strings starting with that and a period; if no prefix was
1256
-     *                                        specified we return all items containing NO periods
1257
-     */
1258
-    public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1259
-    {
1260
-        if (is_string($string_to_explode)) {
1261
-            $exploded_contents = explode(',', $string_to_explode);
1262
-        } elseif (is_array($string_to_explode)) {
1263
-            $exploded_contents = $string_to_explode;
1264
-        } else {
1265
-            $exploded_contents = array();
1266
-        }
1267
-        //if the string was empty, we want an empty array
1268
-        $exploded_contents = array_filter($exploded_contents);
1269
-        $contents_with_prefix = array();
1270
-        foreach ($exploded_contents as $item) {
1271
-            $item = trim($item);
1272
-            //if no prefix was provided, so we look for items with no "." in them
1273
-            if (! $prefix) {
1274
-                //does this item have a period?
1275
-                if (strpos($item, '.') === false) {
1276
-                    //if not, then its what we're looking for
1277
-                    $contents_with_prefix[] = $item;
1278
-                }
1279
-            } elseif (strpos($item, $prefix . '.') === 0) {
1280
-                //this item has the prefix and a period, grab it
1281
-                $contents_with_prefix[] = substr(
1282
-                    $item,
1283
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1284
-                );
1285
-            } elseif ($item === $prefix) {
1286
-                //this item is JUST the prefix
1287
-                //so let's grab everything after, which is a blank string
1288
-                $contents_with_prefix[] = '';
1289
-            }
1290
-        }
1291
-        return $contents_with_prefix;
1292
-    }
1293
-
1294
-
1295
-
1296
-    /**
1297
-     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1298
-     * Deprecated because its return values were really quite confusing- sometimes it returned
1299
-     * an empty array (when the include string was blank or '*') or sometimes it returned
1300
-     * array('*') (when you provided a model and a model of that kind was found).
1301
-     * Parses the $include_string so we fetch all the field names relating to THIS model
1302
-     * (ie have NO period in them), or for the provided model (ie start with the model
1303
-     * name and then a period).
1304
-     * @param string $include_string @see Read:handle_request_get_all
1305
-     * @param string $model_name
1306
-     * @return array of fields for this model. If $model_name is provided, then
1307
-     *                               the fields for that model, with the model's name removed from each.
1308
-     *                               If $include_string was blank or '*' returns an empty array
1309
-     */
1310
-    public function extractIncludesForThisModel($include_string, $model_name = null)
1311
-    {
1312
-        if (is_array($include_string)) {
1313
-            $include_string = implode(',', $include_string);
1314
-        }
1315
-        if ($include_string === '*' || $include_string === '') {
1316
-            return array();
1317
-        }
1318
-        $includes = explode(',', $include_string);
1319
-        $extracted_fields_to_include = array();
1320
-        if ($model_name) {
1321
-            foreach ($includes as $field_to_include) {
1322
-                $field_to_include = trim($field_to_include);
1323
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1324
-                    //found the model name at the exact start
1325
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1326
-                    $extracted_fields_to_include[] = $field_sans_model_name;
1327
-                } elseif ($field_to_include == $model_name) {
1328
-                    $extracted_fields_to_include[] = '*';
1329
-                }
1330
-            }
1331
-        } else {
1332
-            //look for ones with no period
1333
-            foreach ($includes as $field_to_include) {
1334
-                $field_to_include = trim($field_to_include);
1335
-                if (strpos($field_to_include, '.') === false
1336
-                    && ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1337
-                ) {
1338
-                    $extracted_fields_to_include[] = $field_to_include;
1339
-                }
1340
-            }
1341
-        }
1342
-        return $extracted_fields_to_include;
1343
-    }
1344
-
1345
-
1346
-
1347
-    /**
1348
-     * Gets the single item using the model according to the request in the context given, otherwise
1349
-     * returns that it's inaccessible to the current user
1350
-
1351
-     *
503
+	 * @param \EE_Model_Relation_Base $relation
504
+	 * @param WP_REST_Request        $request
505
+	 * @return array|WP_Error
506
+	 * @throws EE_Error
507
+	 */
508
+	public function getEntitiesFromRelation($id, $relation, $request)
509
+	{
510
+		if (! $relation->get_this_model()->has_primary_key_field()) {
511
+			throw new EE_Error(
512
+				sprintf(
513
+					__(
514
+						// @codingStandardsIgnoreStart
515
+						'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
516
+						// @codingStandardsIgnoreEnd
517
+						'event_espresso'
518
+					),
519
+					$relation->get_this_model()->get_this_model_name()
520
+				)
521
+			);
522
+		}
523
+		return $this->getEntitiesFromRelationUsingModelQueryParams(
524
+			array(
525
+				array(
526
+					$relation->get_this_model()->primary_key_name() => $id,
527
+				),
528
+			),
529
+			$relation,
530
+			$request
531
+		);
532
+	}
533
+
534
+
535
+
536
+	/**
537
+	 * Sets the headers that are based on the model and query params,
538
+	 * like the total records. This should only be called on the original request
539
+	 * from the client, not on subsequent internal
540
+	 *
541
+	 * @param EEM_Base $model
542
+	 * @param array     $query_params
543
+	 * @return void
544
+	 */
545
+	protected function setHeadersFromQueryParams($model, $query_params)
546
+	{
547
+		$this->setDebugInfo('model query params', $query_params);
548
+		$this->setDebugInfo(
549
+			'missing caps',
550
+			Capabilities::getMissingPermissionsString($model, $query_params['caps'])
551
+		);
552
+		//normally the limit to a 2-part array, where the 2nd item is the limit
553
+		if (! isset($query_params['limit'])) {
554
+			$query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
555
+		}
556
+		if (is_array($query_params['limit'])) {
557
+			$limit_parts = $query_params['limit'];
558
+		} else {
559
+			$limit_parts = explode(',', $query_params['limit']);
560
+			if (count($limit_parts) == 1) {
561
+				$limit_parts = array(0, $limit_parts[0]);
562
+			}
563
+		}
564
+		//remove the group by and having parts of the query, as those will
565
+		//make the sql query return an array of values, instead of just a single value
566
+		unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
567
+		$count = $model->count($query_params, null, true);
568
+		$pages = $count / $limit_parts[1];
569
+		$this->setResponseHeader('Total', $count, false);
570
+		$this->setResponseHeader('PageSize', $limit_parts[1], false);
571
+		$this->setResponseHeader('TotalPages', ceil($pages), false);
572
+	}
573
+
574
+
575
+
576
+	/**
577
+	 * Changes database results into REST API entities
578
+	 *
579
+	 * @param EEM_Base        $model
580
+	 * @param array            $db_row     like results from $wpdb->get_results()
581
+	 * @param WP_REST_Request $rest_request
582
+	 * @param string           $deprecated no longer used
583
+	 * @return array ready for being converted into json for sending to client
584
+	 */
585
+	public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
586
+	{
587
+		if (! $rest_request instanceof WP_REST_Request) {
588
+			//ok so this was called in the old style, where the 3rd arg was
589
+			//$include, and the 4th arg was $context
590
+			//now setup the request just to avoid fatal errors, although we won't be able
591
+			//to truly make use of it because it's kinda devoid of info
592
+			$rest_request = new WP_REST_Request();
593
+			$rest_request->set_param('include', $rest_request);
594
+			$rest_request->set_param('caps', $deprecated);
595
+		}
596
+		if ($rest_request->get_param('caps') == null) {
597
+			$rest_request->set_param('caps', EEM_Base::caps_read);
598
+		}
599
+		$entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
600
+		$entity_array = $this->addExtraFields($model, $db_row, $entity_array);
601
+		$entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
602
+		$entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request);
603
+		$entity_array = apply_filters(
604
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
605
+			$entity_array,
606
+			$model,
607
+			$rest_request->get_param('caps'),
608
+			$rest_request,
609
+			$this
610
+		);
611
+		$entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row);
612
+		$entity_array = apply_filters(
613
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
614
+			$entity_array,
615
+			$model,
616
+			$rest_request->get_param('caps'),
617
+			$rest_request,
618
+			$this
619
+		);
620
+		$result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
621
+			$entity_array,
622
+			$model,
623
+			$rest_request->get_param('caps'),
624
+			$this->getModelVersionInfo(),
625
+			$model->get_index_primary_key_string(
626
+				$model->deduce_fields_n_values_from_cols_n_values($db_row)
627
+			)
628
+		);
629
+		$this->setDebugInfo(
630
+			'inaccessible fields',
631
+			array_keys(array_diff_key($entity_array, $result_without_inaccessible_fields))
632
+		);
633
+		return apply_filters(
634
+			'FHEE__Read__create_entity_from_wpdb_results__entity_return',
635
+			$result_without_inaccessible_fields,
636
+			$model,
637
+			$rest_request->get_param('caps')
638
+		);
639
+	}
640
+
641
+
642
+
643
+	/**
644
+	 * Creates a REST entity array (JSON object we're going to return in the response, but
645
+	 * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
646
+	 * from $wpdb->get_row( $sql, ARRAY_A)
647
+	 *
648
+	 * @param EEM_Base $model
649
+	 * @param array     $db_row
650
+	 * @return array entity mostly ready for converting to JSON and sending in the response
651
+	 *
652
+	 */
653
+	protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
654
+	{
655
+		$result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
656
+		$result = array_intersect_key(
657
+			$result,
658
+			$this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
659
+		);
660
+		//if this is a CPT, we need to set the global $post to it,
661
+		//otherwise shortcodes etc won't work properly while rendering it
662
+		if ($model instanceof \EEM_CPT_Base) {
663
+			$do_chevy_shuffle = true;
664
+		} else {
665
+			$do_chevy_shuffle = false;
666
+		}
667
+		if ($do_chevy_shuffle) {
668
+			global $post;
669
+			$old_post = $post;
670
+			$post = get_post($result[$model->primary_key_name()]);
671
+			if (! $post instanceof \WP_Post) {
672
+				//well that's weird, because $result is what we JUST fetched from the database
673
+				throw new RestException(
674
+					'error_fetching_post_from_database_results',
675
+					esc_html__(
676
+						'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
677
+						'event_espresso'
678
+					)
679
+				);
680
+			}
681
+			$model_object_classname = 'EE_' . $model->get_this_model_name();
682
+			$post->{$model_object_classname} = \EE_Registry::instance()->load_class(
683
+				$model_object_classname,
684
+				$result,
685
+				false,
686
+				false
687
+			);
688
+		}
689
+		foreach ($result as $field_name => $field_value) {
690
+			$field_obj = $model->field_settings_for($field_name);
691
+			if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
692
+				unset($result[$field_name]);
693
+			} elseif ($this->isSubclassOfOne(
694
+				$field_obj,
695
+				$this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
696
+			)
697
+			) {
698
+				$result[$field_name] = array(
699
+					'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
700
+					'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
701
+				);
702
+			} elseif ($this->isSubclassOfOne(
703
+				$field_obj,
704
+				$this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
705
+			)
706
+			) {
707
+				$result[$field_name] = array(
708
+					'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
709
+					'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
710
+				);
711
+			} elseif ($field_obj instanceof \EE_Datetime_Field) {
712
+				$field_value = $field_obj->prepare_for_set_from_db($field_value);
713
+				$timezone = $field_value->getTimezone();
714
+				EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
715
+				$result[$field_name . '_gmt'] = ModelDataTranslator::prepareFieldValuesForJson(
716
+					$field_obj,
717
+					$field_value,
718
+					$this->getModelVersionInfo()->requestedVersion()
719
+				);
720
+				EEH_DTT_Helper::setTimezone($field_value, $timezone);
721
+				$result[$field_name] = ModelDataTranslator::prepareFieldValuesForJson(
722
+					$field_obj,
723
+					$field_value,
724
+					$this->getModelVersionInfo()->requestedVersion()
725
+				);
726
+			} else {
727
+				$result[$field_name] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
728
+			}
729
+		}
730
+		if ($do_chevy_shuffle) {
731
+			$post = $old_post;
732
+		}
733
+		return $result;
734
+	}
735
+
736
+
737
+
738
+	/**
739
+	 * Takes a value all the way from the DB representation, to the model object's representation, to the
740
+	 * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
741
+	 * representation using $field_obj->prepare_for_set_from_db())
742
+	 *
743
+	 * @param EE_Model_Field_Base $field_obj
744
+	 * @param mixed $value as it's stored on a model object
745
+	 * @param string $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
746
+	 * @return mixed
747
+	 * @throws ObjectDetectedException if $value contains a PHP object
748
+	 */
749
+	protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
750
+	{
751
+		$value = $field_obj->prepare_for_set_from_db($value);
752
+		switch ($format) {
753
+			case 'pretty':
754
+				$value = $field_obj->prepare_for_pretty_echoing($value);
755
+				break;
756
+			case 'normal':
757
+			default:
758
+				$value = $field_obj->prepare_for_get($value);
759
+				break;
760
+		}
761
+		return ModelDataTranslator::prepareFieldValuesForJson(
762
+			$field_obj,
763
+			$value,
764
+			$this->getModelVersionInfo()->requestedVersion()
765
+		);
766
+	}
767
+
768
+
769
+
770
+	/**
771
+	 * Adds a few extra fields to the entity response
772
+	 *
773
+	 * @param EEM_Base $model
774
+	 * @param array     $db_row
775
+	 * @param array     $entity_array
776
+	 * @return array modified entity
777
+	 */
778
+	protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
779
+	{
780
+		if ($model instanceof EEM_CPT_Base) {
781
+			$entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
782
+		}
783
+		return $entity_array;
784
+	}
785
+
786
+
787
+
788
+	/**
789
+	 * Gets links we want to add to the response
790
+	 *
791
+	 * @global \WP_REST_Server $wp_rest_server
792
+	 * @param EEM_Base        $model
793
+	 * @param array            $db_row
794
+	 * @param array            $entity_array
795
+	 * @return array the _links item in the entity
796
+	 */
797
+	protected function getEntityLinks($model, $db_row, $entity_array)
798
+	{
799
+		//add basic links
800
+		$links = array();
801
+		if ($model->has_primary_key_field()) {
802
+			$links['self'] = array(
803
+				array(
804
+					'href' => $this->getVersionedLinkTo(
805
+						EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
806
+						. '/'
807
+						. $entity_array[$model->primary_key_name()]
808
+					),
809
+				),
810
+			);
811
+		}
812
+		$links['collection'] = array(
813
+			array(
814
+				'href' => $this->getVersionedLinkTo(
815
+					EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
816
+				),
817
+			),
818
+		);
819
+		//add links to related models
820
+		if ($model->has_primary_key_field()) {
821
+			foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
822
+				$related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
823
+				$links[EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part] = array(
824
+					array(
825
+						'href'   => $this->getVersionedLinkTo(
826
+							EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
827
+							. '/'
828
+							. $entity_array[$model->primary_key_name()]
829
+							. '/'
830
+							. $related_model_part
831
+						),
832
+						'single' => $relation_obj instanceof EE_Belongs_To_Relation ? true : false,
833
+					),
834
+				);
835
+			}
836
+		}
837
+		return $links;
838
+	}
839
+
840
+
841
+
842
+	/**
843
+	 * Adds the included models indicated in the request to the entity provided
844
+	 *
845
+	 * @param EEM_Base        $model
846
+	 * @param WP_REST_Request $rest_request
847
+	 * @param array            $entity_array
848
+	 * @param array            $db_row
849
+	 * @return array the modified entity
850
+	 */
851
+	protected function includeRequestedModels(
852
+		EEM_Base $model,
853
+		WP_REST_Request $rest_request,
854
+		$entity_array,
855
+		$db_row = array()
856
+	) {
857
+		//if $db_row not included, hope the entity array has what we need
858
+		if (! $db_row) {
859
+			$db_row = $entity_array;
860
+		}
861
+		$includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
862
+		$includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
863
+		//if they passed in * or didn't specify any includes, return everything
864
+		if (! in_array('*', $includes_for_this_model)
865
+			&& ! empty($includes_for_this_model)
866
+		) {
867
+			if ($model->has_primary_key_field()) {
868
+				//always include the primary key. ya just gotta know that at least
869
+				$includes_for_this_model[] = $model->primary_key_name();
870
+			}
871
+			if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
872
+				$includes_for_this_model[] = '_calculated_fields';
873
+			}
874
+			$entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
875
+		}
876
+		$relation_settings = $this->getModelVersionInfo()->relationSettings($model);
877
+		foreach ($relation_settings as $relation_name => $relation_obj) {
878
+			$related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
879
+				$rest_request->get_param('include'),
880
+				$relation_name
881
+			);
882
+			$related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
883
+				$rest_request->get_param('calculate'),
884
+				$relation_name
885
+			);
886
+			//did they specify they wanted to include a related model, or
887
+			//specific fields from a related model?
888
+			//or did they specify to calculate a field from a related model?
889
+			if ($related_fields_to_include || $related_fields_to_calculate) {
890
+				//if so, we should include at least some part of the related model
891
+				$pretend_related_request = new WP_REST_Request();
892
+				$pretend_related_request->set_query_params(
893
+					array(
894
+						'caps'      => $rest_request->get_param('caps'),
895
+						'include'   => $related_fields_to_include,
896
+						'calculate' => $related_fields_to_calculate,
897
+					)
898
+				);
899
+				$pretend_related_request->add_header('no_rest_headers', true);
900
+				$primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
901
+					$model->get_index_primary_key_string(
902
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
903
+					)
904
+				);
905
+				$related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
906
+					$primary_model_query_params,
907
+					$relation_obj,
908
+					$pretend_related_request
909
+				);
910
+				$entity_array[Read::getRelatedEntityName($relation_name, $relation_obj)] = $related_results
911
+																						   instanceof
912
+																						   WP_Error
913
+					? null
914
+					: $related_results;
915
+			}
916
+		}
917
+		return $entity_array;
918
+	}
919
+
920
+
921
+
922
+	/**
923
+	 * Returns a new array with all the names of models removed. Eg
924
+	 * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
925
+	 *
926
+	 * @param array $arr
927
+	 * @return array
928
+	 */
929
+	private function removeModelNamesFromArray($arr)
930
+	{
931
+		return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
932
+	}
933
+
934
+
935
+
936
+	/**
937
+	 * Gets the calculated fields for the response
938
+	 *
939
+	 * @param EEM_Base        $model
940
+	 * @param array            $wpdb_row
941
+	 * @param WP_REST_Request $rest_request
942
+	 * @return \stdClass the _calculations item in the entity
943
+	 * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
944
+	 * did, let's know about it ASAP, so let the exception bubble up)
945
+	 */
946
+	protected function getEntityCalculations($model, $wpdb_row, $rest_request)
947
+	{
948
+		$calculated_fields = $this->explodeAndGetItemsPrefixedWith(
949
+			$rest_request->get_param('calculate'),
950
+			''
951
+		);
952
+		//note: setting calculate=* doesn't do anything
953
+		$calculated_fields_to_return = new \stdClass();
954
+		foreach ($calculated_fields as $field_to_calculate) {
955
+			try {
956
+				$calculated_fields_to_return->$field_to_calculate = ModelDataTranslator::prepareFieldValueForJson(
957
+					null,
958
+					$this->fields_calculator->retrieveCalculatedFieldValue(
959
+						$model,
960
+						$field_to_calculate,
961
+						$wpdb_row,
962
+						$rest_request,
963
+						$this
964
+					),
965
+					$this->getModelVersionInfo()->requestedVersion()
966
+				);
967
+			} catch (RestException $e) {
968
+				//if we don't have permission to read it, just leave it out. but let devs know about the problem
969
+				$this->setResponseHeader(
970
+					'Notices-Field-Calculation-Errors['
971
+					. $e->getStringCode()
972
+					. ']['
973
+					. $model->get_this_model_name()
974
+					. ']['
975
+					. $field_to_calculate
976
+					. ']',
977
+					$e->getMessage(),
978
+					true
979
+				);
980
+			}
981
+		}
982
+		return $calculated_fields_to_return;
983
+	}
984
+
985
+
986
+
987
+	/**
988
+	 * Gets the full URL to the resource, taking the requested version into account
989
+	 *
990
+	 * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
991
+	 * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
992
+	 */
993
+	public function getVersionedLinkTo($link_part_after_version_and_slash)
994
+	{
995
+		return rest_url(
996
+			EED_Core_Rest_Api::get_versioned_route_to(
997
+				$link_part_after_version_and_slash,
998
+				$this->getModelVersionInfo()->requestedVersion()
999
+			)
1000
+		);
1001
+	}
1002
+
1003
+
1004
+
1005
+	/**
1006
+	 * Gets the correct lowercase name for the relation in the API according
1007
+	 * to the relation's type
1008
+	 *
1009
+	 * @param string                  $relation_name
1010
+	 * @param \EE_Model_Relation_Base $relation_obj
1011
+	 * @return string
1012
+	 */
1013
+	public static function getRelatedEntityName($relation_name, $relation_obj)
1014
+	{
1015
+		if ($relation_obj instanceof EE_Belongs_To_Relation) {
1016
+			return strtolower($relation_name);
1017
+		} else {
1018
+			return EEH_Inflector::pluralize_and_lower($relation_name);
1019
+		}
1020
+	}
1021
+
1022
+
1023
+
1024
+	/**
1025
+	 * Gets the one model object with the specified id for the specified model
1026
+	 *
1027
+	 * @param EEM_Base        $model
1028
+	 * @param WP_REST_Request $request
1029
+	 * @return array|WP_Error
1030
+	 */
1031
+	public function getEntityFromModel($model, $request)
1032
+	{
1033
+		$context = $this->validateContext($request->get_param('caps'));
1034
+		return $this->getOneOrReportPermissionError($model, $request, $context);
1035
+	}
1036
+
1037
+
1038
+
1039
+	/**
1040
+	 * If a context is provided which isn't valid, maybe it was added in a future
1041
+	 * version so just treat it as a default read
1042
+	 *
1043
+	 * @param string $context
1044
+	 * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1045
+	 */
1046
+	public function validateContext($context)
1047
+	{
1048
+		if (! $context) {
1049
+			$context = EEM_Base::caps_read;
1050
+		}
1051
+		$valid_contexts = EEM_Base::valid_cap_contexts();
1052
+		if (in_array($context, $valid_contexts)) {
1053
+			return $context;
1054
+		} else {
1055
+			return EEM_Base::caps_read;
1056
+		}
1057
+	}
1058
+
1059
+
1060
+
1061
+	/**
1062
+	 * Verifies the passed in value is an allowable default where conditions value.
1063
+	 *
1064
+	 * @param $default_query_params
1065
+	 * @return string
1066
+	 */
1067
+	public function validateDefaultQueryParams($default_query_params)
1068
+	{
1069
+		$valid_default_where_conditions_for_api_calls = array(
1070
+			EEM_Base::default_where_conditions_all,
1071
+			EEM_Base::default_where_conditions_minimum_all,
1072
+			EEM_Base::default_where_conditions_minimum_others,
1073
+		);
1074
+		if (! $default_query_params) {
1075
+			$default_query_params = EEM_Base::default_where_conditions_all;
1076
+		}
1077
+		if (in_array(
1078
+			$default_query_params,
1079
+			$valid_default_where_conditions_for_api_calls,
1080
+			true
1081
+		)) {
1082
+			return $default_query_params;
1083
+		} else {
1084
+			return EEM_Base::default_where_conditions_all;
1085
+		}
1086
+	}
1087
+
1088
+
1089
+
1090
+	/**
1091
+	 * Translates API filter get parameter into $query_params array used by EEM_Base::get_all().
1092
+	 * Note: right now the query parameter keys for fields (and related fields)
1093
+	 * can be left as-is, but it's quite possible this will change someday.
1094
+	 * Also, this method's contents might be candidate for moving to Model_Data_Translator
1095
+	 *
1096
+	 * @param EEM_Base $model
1097
+	 * @param array     $query_parameters from $_GET parameter @see Read:handle_request_get_all
1098
+	 * @return array like what EEM_Base::get_all() expects or FALSE to indicate
1099
+	 *                                    that absolutely no results should be returned
1100
+	 * @throws EE_Error
1101
+	 * @throws RestException
1102
+	 */
1103
+	public function createModelQueryParams($model, $query_parameters)
1104
+	{
1105
+		$model_query_params = array();
1106
+		if (isset($query_parameters['where'])) {
1107
+			$model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1108
+				$query_parameters['where'],
1109
+				$model,
1110
+				$this->getModelVersionInfo()->requestedVersion()
1111
+			);
1112
+		}
1113
+		if (isset($query_parameters['order_by'])) {
1114
+			$order_by = $query_parameters['order_by'];
1115
+		} elseif (isset($query_parameters['orderby'])) {
1116
+			$order_by = $query_parameters['orderby'];
1117
+		} else {
1118
+			$order_by = null;
1119
+		}
1120
+		if ($order_by !== null) {
1121
+			if (is_array($order_by)) {
1122
+				$order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1123
+			} else {
1124
+				//it's a single item
1125
+				$order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1126
+			}
1127
+			$model_query_params['order_by'] = $order_by;
1128
+		}
1129
+		if (isset($query_parameters['group_by'])) {
1130
+			$group_by = $query_parameters['group_by'];
1131
+		} elseif (isset($query_parameters['groupby'])) {
1132
+			$group_by = $query_parameters['groupby'];
1133
+		} else {
1134
+			$group_by = array_keys($model->get_combined_primary_key_fields());
1135
+		}
1136
+		//make sure they're all real names
1137
+		if (is_array($group_by)) {
1138
+			$group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1139
+		}
1140
+		if ($group_by !== null) {
1141
+			$model_query_params['group_by'] = $group_by;
1142
+		}
1143
+		if (isset($query_parameters['having'])) {
1144
+			$model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1145
+				$query_parameters['having'],
1146
+				$model,
1147
+				$this->getModelVersionInfo()->requestedVersion()
1148
+			);
1149
+		}
1150
+		if (isset($query_parameters['order'])) {
1151
+			$model_query_params['order'] = $query_parameters['order'];
1152
+		}
1153
+		if (isset($query_parameters['mine'])) {
1154
+			$model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1155
+		}
1156
+		if (isset($query_parameters['limit'])) {
1157
+			//limit should be either a string like '23' or '23,43', or an array with two items in it
1158
+			if (! is_array($query_parameters['limit'])) {
1159
+				$limit_array = explode(',', (string)$query_parameters['limit']);
1160
+			} else {
1161
+				$limit_array = $query_parameters['limit'];
1162
+			}
1163
+			$sanitized_limit = array();
1164
+			foreach ($limit_array as $key => $limit_part) {
1165
+				if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1166
+					throw new EE_Error(
1167
+						sprintf(
1168
+							__(
1169
+								// @codingStandardsIgnoreStart
1170
+								'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1171
+								// @codingStandardsIgnoreEnd
1172
+								'event_espresso'
1173
+							),
1174
+							wp_json_encode($query_parameters['limit'])
1175
+						)
1176
+					);
1177
+				}
1178
+				$sanitized_limit[] = (int)$limit_part;
1179
+			}
1180
+			$model_query_params['limit'] = implode(',', $sanitized_limit);
1181
+		} else {
1182
+			$model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1183
+		}
1184
+		if (isset($query_parameters['caps'])) {
1185
+			$model_query_params['caps'] = $this->validateContext($query_parameters['caps']);
1186
+		} else {
1187
+			$model_query_params['caps'] = EEM_Base::caps_read;
1188
+		}
1189
+		if (isset($query_parameters['default_where_conditions'])) {
1190
+			$model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1191
+				$query_parameters['default_where_conditions']
1192
+			);
1193
+		}
1194
+		return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_parameters, $model);
1195
+	}
1196
+
1197
+
1198
+
1199
+	/**
1200
+	 * Changes the REST-style query params for use in the models
1201
+	 *
1202
+	 * @deprecated
1203
+	 * @param EEM_Base $model
1204
+	 * @param array     $query_params sub-array from @see EEM_Base::get_all()
1205
+	 * @return array
1206
+	 */
1207
+	public function prepareRestQueryParamsKeyForModels($model, $query_params)
1208
+	{
1209
+		$model_ready_query_params = array();
1210
+		foreach ($query_params as $key => $value) {
1211
+			if (is_array($value)) {
1212
+				$model_ready_query_params[$key] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1213
+			} else {
1214
+				$model_ready_query_params[$key] = $value;
1215
+			}
1216
+		}
1217
+		return $model_ready_query_params;
1218
+	}
1219
+
1220
+
1221
+
1222
+	/**
1223
+	 * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1224
+	 * @param $model
1225
+	 * @param $query_params
1226
+	 * @return array
1227
+	 */
1228
+	public function prepareRestQueryParamsValuesForModels($model, $query_params)
1229
+	{
1230
+		$model_ready_query_params = array();
1231
+		foreach ($query_params as $key => $value) {
1232
+			if (is_array($value)) {
1233
+				$model_ready_query_params[$key] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1234
+			} else {
1235
+				$model_ready_query_params[$key] = $value;
1236
+			}
1237
+		}
1238
+		return $model_ready_query_params;
1239
+	}
1240
+
1241
+
1242
+
1243
+	/**
1244
+	 * Explodes the string on commas, and only returns items with $prefix followed by a period.
1245
+	 * If no prefix is specified, returns items with no period.
1246
+	 *
1247
+	 * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1248
+	 * @param string       $prefix            "Event" or "foobar"
1249
+	 * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1250
+	 *                                        we only return strings starting with that and a period; if no prefix was
1251
+	 *                                        specified we return all items containing NO periods
1252
+	 */
1253
+	public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1254
+	{
1255
+		if (is_string($string_to_explode)) {
1256
+			$exploded_contents = explode(',', $string_to_explode);
1257
+		} elseif (is_array($string_to_explode)) {
1258
+			$exploded_contents = $string_to_explode;
1259
+		} else {
1260
+			$exploded_contents = array();
1261
+		}
1262
+		//if the string was empty, we want an empty array
1263
+		$exploded_contents = array_filter($exploded_contents);
1264
+		$contents_with_prefix = array();
1265
+		foreach ($exploded_contents as $item) {
1266
+			$item = trim($item);
1267
+			//if no prefix was provided, so we look for items with no "." in them
1268
+			if (! $prefix) {
1269
+				//does this item have a period?
1270
+				if (strpos($item, '.') === false) {
1271
+					//if not, then its what we're looking for
1272
+					$contents_with_prefix[] = $item;
1273
+				}
1274
+			} elseif (strpos($item, $prefix . '.') === 0) {
1275
+				//this item has the prefix and a period, grab it
1276
+				$contents_with_prefix[] = substr(
1277
+					$item,
1278
+					strpos($item, $prefix . '.') + strlen($prefix . '.')
1279
+				);
1280
+			} elseif ($item === $prefix) {
1281
+				//this item is JUST the prefix
1282
+				//so let's grab everything after, which is a blank string
1283
+				$contents_with_prefix[] = '';
1284
+			}
1285
+		}
1286
+		return $contents_with_prefix;
1287
+	}
1288
+
1289
+
1290
+
1291
+	/**
1292
+	 * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1293
+	 * Deprecated because its return values were really quite confusing- sometimes it returned
1294
+	 * an empty array (when the include string was blank or '*') or sometimes it returned
1295
+	 * array('*') (when you provided a model and a model of that kind was found).
1296
+	 * Parses the $include_string so we fetch all the field names relating to THIS model
1297
+	 * (ie have NO period in them), or for the provided model (ie start with the model
1298
+	 * name and then a period).
1299
+	 * @param string $include_string @see Read:handle_request_get_all
1300
+	 * @param string $model_name
1301
+	 * @return array of fields for this model. If $model_name is provided, then
1302
+	 *                               the fields for that model, with the model's name removed from each.
1303
+	 *                               If $include_string was blank or '*' returns an empty array
1304
+	 */
1305
+	public function extractIncludesForThisModel($include_string, $model_name = null)
1306
+	{
1307
+		if (is_array($include_string)) {
1308
+			$include_string = implode(',', $include_string);
1309
+		}
1310
+		if ($include_string === '*' || $include_string === '') {
1311
+			return array();
1312
+		}
1313
+		$includes = explode(',', $include_string);
1314
+		$extracted_fields_to_include = array();
1315
+		if ($model_name) {
1316
+			foreach ($includes as $field_to_include) {
1317
+				$field_to_include = trim($field_to_include);
1318
+				if (strpos($field_to_include, $model_name . '.') === 0) {
1319
+					//found the model name at the exact start
1320
+					$field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1321
+					$extracted_fields_to_include[] = $field_sans_model_name;
1322
+				} elseif ($field_to_include == $model_name) {
1323
+					$extracted_fields_to_include[] = '*';
1324
+				}
1325
+			}
1326
+		} else {
1327
+			//look for ones with no period
1328
+			foreach ($includes as $field_to_include) {
1329
+				$field_to_include = trim($field_to_include);
1330
+				if (strpos($field_to_include, '.') === false
1331
+					&& ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1332
+				) {
1333
+					$extracted_fields_to_include[] = $field_to_include;
1334
+				}
1335
+			}
1336
+		}
1337
+		return $extracted_fields_to_include;
1338
+	}
1339
+
1340
+
1341
+
1342
+	/**
1343
+	 * Gets the single item using the model according to the request in the context given, otherwise
1344
+	 * returns that it's inaccessible to the current user
1345
+	 *
1352 1346
 *@param EEM_Base        $model
1353
-     * @param WP_REST_Request $request
1354
-     * @param null             $context
1355
-     * @return array|WP_Error
1356
-     */
1357
-    public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1358
-    {
1359
-        $query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1360
-        if ($model instanceof \EEM_Soft_Delete_Base) {
1361
-            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1362
-        }
1363
-        $restricted_query_params = $query_params;
1364
-        $restricted_query_params['caps'] = $context;
1365
-        $this->setDebugInfo('model query params', $restricted_query_params);
1366
-        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1367
-        if (! empty($model_rows)) {
1368
-            return $this->createEntityFromWpdbResult(
1369
-                $model,
1370
-                array_shift($model_rows),
1371
-                $request
1372
-            );
1373
-        } else {
1374
-            //ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1375
-            $lowercase_model_name = strtolower($model->get_this_model_name());
1376
-            $model_rows_found_sans_restrictions = $model->get_all_wpdb_results($query_params);
1377
-            if (! empty($model_rows_found_sans_restrictions)) {
1378
-                //you got shafted- it existed but we didn't want to tell you!
1379
-                return new WP_Error(
1380
-                    'rest_user_cannot_' . $context,
1381
-                    sprintf(
1382
-                        __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1383
-                        $context,
1384
-                        strtolower($model->get_this_model_name()),
1385
-                        Capabilities::getMissingPermissionsString(
1386
-                            $model,
1387
-                            $context
1388
-                        )
1389
-                    ),
1390
-                    array('status' => 403)
1391
-                );
1392
-            } else {
1393
-                //it's not you. It just doesn't exist
1394
-                return new WP_Error(
1395
-                    sprintf('rest_%s_invalid_id', $lowercase_model_name),
1396
-                    sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1397
-                    array('status' => 404)
1398
-                );
1399
-            }
1400
-        }
1401
-    }
1347
+	 * @param WP_REST_Request $request
1348
+	 * @param null             $context
1349
+	 * @return array|WP_Error
1350
+	 */
1351
+	public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1352
+	{
1353
+		$query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1354
+		if ($model instanceof \EEM_Soft_Delete_Base) {
1355
+			$query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1356
+		}
1357
+		$restricted_query_params = $query_params;
1358
+		$restricted_query_params['caps'] = $context;
1359
+		$this->setDebugInfo('model query params', $restricted_query_params);
1360
+		$model_rows = $model->get_all_wpdb_results($restricted_query_params);
1361
+		if (! empty($model_rows)) {
1362
+			return $this->createEntityFromWpdbResult(
1363
+				$model,
1364
+				array_shift($model_rows),
1365
+				$request
1366
+			);
1367
+		} else {
1368
+			//ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1369
+			$lowercase_model_name = strtolower($model->get_this_model_name());
1370
+			$model_rows_found_sans_restrictions = $model->get_all_wpdb_results($query_params);
1371
+			if (! empty($model_rows_found_sans_restrictions)) {
1372
+				//you got shafted- it existed but we didn't want to tell you!
1373
+				return new WP_Error(
1374
+					'rest_user_cannot_' . $context,
1375
+					sprintf(
1376
+						__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1377
+						$context,
1378
+						strtolower($model->get_this_model_name()),
1379
+						Capabilities::getMissingPermissionsString(
1380
+							$model,
1381
+							$context
1382
+						)
1383
+					),
1384
+					array('status' => 403)
1385
+				);
1386
+			} else {
1387
+				//it's not you. It just doesn't exist
1388
+				return new WP_Error(
1389
+					sprintf('rest_%s_invalid_id', $lowercase_model_name),
1390
+					sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1391
+					array('status' => 404)
1392
+				);
1393
+			}
1394
+		}
1395
+	}
1402 1396
 }
1403 1397
 
1404 1398
 
Please login to merge, or discard this patch.
Spacing   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -22,7 +22,7 @@  discard block
 block discarded – undo
22 22
 use EEM_Base;
23 23
 use EEM_CPT_Base;
24 24
 
25
-if (! defined('EVENT_ESPRESSO_VERSION')) {
25
+if ( ! defined('EVENT_ESPRESSO_VERSION')) {
26 26
     exit('No direct script access allowed');
27 27
 }
28 28
 
@@ -73,7 +73,7 @@  discard block
 block discarded – undo
73 73
         $controller = new Read();
74 74
         try {
75 75
             $controller->setRequestedVersion($version);
76
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
76
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
77 77
                 return $controller->sendResponse(
78 78
                     new WP_Error(
79 79
                         'endpoint_parsing_error',
@@ -112,7 +112,7 @@  discard block
 block discarded – undo
112 112
         $controller = new Read();
113 113
         try {
114 114
             $controller->setRequestedVersion($version);
115
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
115
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
116 116
                 return array();
117 117
             }
118 118
             //get the model for this version
@@ -209,9 +209,9 @@  discard block
 block discarded – undo
209 209
     protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
210 210
     {
211 211
         if ($field instanceof EE_Datetime_Field) {
212
-            $schema['properties'][$field_name . '_gmt'] = $field->getSchema();
212
+            $schema['properties'][$field_name.'_gmt'] = $field->getSchema();
213 213
             //modify the description
214
-            $schema['properties'][$field_name . '_gmt']['description'] = sprintf(
214
+            $schema['properties'][$field_name.'_gmt']['description'] = sprintf(
215 215
                 esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
216 216
                 wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
217 217
             );
@@ -254,7 +254,7 @@  discard block
 block discarded – undo
254 254
         $controller = new Read();
255 255
         try {
256 256
             $controller->setRequestedVersion($version);
257
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
257
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
258 258
                 return $controller->sendResponse(
259 259
                     new WP_Error(
260 260
                         'endpoint_parsing_error',
@@ -301,7 +301,7 @@  discard block
 block discarded – undo
301 301
         $controller = new Read();
302 302
         try {
303 303
             $controller->setRequestedVersion($version);
304
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
304
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
305 305
                 return $controller->sendResponse(
306 306
                     new WP_Error(
307 307
                         'endpoint_parsing_error',
@@ -316,7 +316,7 @@  discard block
 block discarded – undo
316 316
                 );
317 317
             }
318 318
             $main_model = $controller->getModelVersionInfo()->loadModel($model_name);
319
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
319
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
320 320
                 return $controller->sendResponse(
321 321
                     new WP_Error(
322 322
                         'endpoint_parsing_error',
@@ -355,7 +355,7 @@  discard block
 block discarded – undo
355 355
     public function getEntitiesFromModel($model, $request)
356 356
     {
357 357
         $query_params = $this->createModelQueryParams($model, $request->get_params());
358
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
358
+        if ( ! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
359 359
             $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
360 360
             return new WP_Error(
361 361
                 sprintf('rest_%s_cannot_list', $model_name_plural),
@@ -367,7 +367,7 @@  discard block
 block discarded – undo
367 367
                 array('status' => 403)
368 368
             );
369 369
         }
370
-        if (! $request->get_header('no_rest_headers')) {
370
+        if ( ! $request->get_header('no_rest_headers')) {
371 371
             $this->setHeadersFromQueryParams($model, $query_params);
372 372
         }
373 373
         /** @type array $results */
@@ -403,7 +403,7 @@  discard block
 block discarded – undo
403 403
         $context = $this->validateContext($request->get_param('caps'));
404 404
         $model = $relation->get_this_model();
405 405
         $related_model = $relation->get_other_model();
406
-        if (! isset($primary_model_query_params[0])) {
406
+        if ( ! isset($primary_model_query_params[0])) {
407 407
             $primary_model_query_params[0] = array();
408 408
         }
409 409
         //check if they can access the 1st model object
@@ -420,7 +420,7 @@  discard block
 block discarded – undo
420 420
         $restricted_query_params['caps'] = $context;
421 421
         $this->setDebugInfo('main model query params', $restricted_query_params);
422 422
         $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
423
-        if (! (
423
+        if ( ! (
424 424
             Capabilities::currentUserHasPartialAccessTo($related_model, $context)
425 425
             && $model->exists($restricted_query_params)
426 426
         )
@@ -459,7 +459,7 @@  discard block
 block discarded – undo
459 459
         }
460 460
         $query_params['default_where_conditions'] = 'none';
461 461
         $query_params['caps'] = $context;
462
-        if (! $request->get_header('no_rest_headers')) {
462
+        if ( ! $request->get_header('no_rest_headers')) {
463 463
             $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
464 464
         }
465 465
         /** @type array $results */
@@ -512,7 +512,7 @@  discard block
 block discarded – undo
512 512
      */
513 513
     public function getEntitiesFromRelation($id, $relation, $request)
514 514
     {
515
-        if (! $relation->get_this_model()->has_primary_key_field()) {
515
+        if ( ! $relation->get_this_model()->has_primary_key_field()) {
516 516
             throw new EE_Error(
517 517
                 sprintf(
518 518
                     __(
@@ -555,7 +555,7 @@  discard block
 block discarded – undo
555 555
             Capabilities::getMissingPermissionsString($model, $query_params['caps'])
556 556
         );
557 557
         //normally the limit to a 2-part array, where the 2nd item is the limit
558
-        if (! isset($query_params['limit'])) {
558
+        if ( ! isset($query_params['limit'])) {
559 559
             $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
560 560
         }
561 561
         if (is_array($query_params['limit'])) {
@@ -589,7 +589,7 @@  discard block
 block discarded – undo
589 589
      */
590 590
     public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
591 591
     {
592
-        if (! $rest_request instanceof WP_REST_Request) {
592
+        if ( ! $rest_request instanceof WP_REST_Request) {
593 593
             //ok so this was called in the old style, where the 3rd arg was
594 594
             //$include, and the 4th arg was $context
595 595
             //now setup the request just to avoid fatal errors, although we won't be able
@@ -673,7 +673,7 @@  discard block
 block discarded – undo
673 673
             global $post;
674 674
             $old_post = $post;
675 675
             $post = get_post($result[$model->primary_key_name()]);
676
-            if (! $post instanceof \WP_Post) {
676
+            if ( ! $post instanceof \WP_Post) {
677 677
                 //well that's weird, because $result is what we JUST fetched from the database
678 678
                 throw new RestException(
679 679
                     'error_fetching_post_from_database_results',
@@ -683,7 +683,7 @@  discard block
 block discarded – undo
683 683
                     )
684 684
                 );
685 685
             }
686
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
686
+            $model_object_classname = 'EE_'.$model->get_this_model_name();
687 687
             $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
688 688
                 $model_object_classname,
689 689
                 $result,
@@ -717,7 +717,7 @@  discard block
 block discarded – undo
717 717
                 $field_value = $field_obj->prepare_for_set_from_db($field_value);
718 718
                 $timezone = $field_value->getTimezone();
719 719
                 EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
720
-                $result[$field_name . '_gmt'] = ModelDataTranslator::prepareFieldValuesForJson(
720
+                $result[$field_name.'_gmt'] = ModelDataTranslator::prepareFieldValuesForJson(
721 721
                     $field_obj,
722 722
                     $field_value,
723 723
                     $this->getModelVersionInfo()->requestedVersion()
@@ -825,7 +825,7 @@  discard block
 block discarded – undo
825 825
         if ($model->has_primary_key_field()) {
826 826
             foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
827 827
                 $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
828
-                $links[EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part] = array(
828
+                $links[EED_Core_Rest_Api::ee_api_link_namespace.$related_model_part] = array(
829 829
                     array(
830 830
                         'href'   => $this->getVersionedLinkTo(
831 831
                             EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
@@ -860,13 +860,13 @@  discard block
 block discarded – undo
860 860
         $db_row = array()
861 861
     ) {
862 862
         //if $db_row not included, hope the entity array has what we need
863
-        if (! $db_row) {
863
+        if ( ! $db_row) {
864 864
             $db_row = $entity_array;
865 865
         }
866 866
         $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
867 867
         $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
868 868
         //if they passed in * or didn't specify any includes, return everything
869
-        if (! in_array('*', $includes_for_this_model)
869
+        if ( ! in_array('*', $includes_for_this_model)
870 870
             && ! empty($includes_for_this_model)
871 871
         ) {
872 872
             if ($model->has_primary_key_field()) {
@@ -1050,7 +1050,7 @@  discard block
 block discarded – undo
1050 1050
      */
1051 1051
     public function validateContext($context)
1052 1052
     {
1053
-        if (! $context) {
1053
+        if ( ! $context) {
1054 1054
             $context = EEM_Base::caps_read;
1055 1055
         }
1056 1056
         $valid_contexts = EEM_Base::valid_cap_contexts();
@@ -1076,7 +1076,7 @@  discard block
 block discarded – undo
1076 1076
             EEM_Base::default_where_conditions_minimum_all,
1077 1077
             EEM_Base::default_where_conditions_minimum_others,
1078 1078
         );
1079
-        if (! $default_query_params) {
1079
+        if ( ! $default_query_params) {
1080 1080
             $default_query_params = EEM_Base::default_where_conditions_all;
1081 1081
         }
1082 1082
         if (in_array(
@@ -1160,14 +1160,14 @@  discard block
 block discarded – undo
1160 1160
         }
1161 1161
         if (isset($query_parameters['limit'])) {
1162 1162
             //limit should be either a string like '23' or '23,43', or an array with two items in it
1163
-            if (! is_array($query_parameters['limit'])) {
1164
-                $limit_array = explode(',', (string)$query_parameters['limit']);
1163
+            if ( ! is_array($query_parameters['limit'])) {
1164
+                $limit_array = explode(',', (string) $query_parameters['limit']);
1165 1165
             } else {
1166 1166
                 $limit_array = $query_parameters['limit'];
1167 1167
             }
1168 1168
             $sanitized_limit = array();
1169 1169
             foreach ($limit_array as $key => $limit_part) {
1170
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1170
+                if ($this->debug_mode && ( ! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1171 1171
                     throw new EE_Error(
1172 1172
                         sprintf(
1173 1173
                             __(
@@ -1180,7 +1180,7 @@  discard block
 block discarded – undo
1180 1180
                         )
1181 1181
                     );
1182 1182
                 }
1183
-                $sanitized_limit[] = (int)$limit_part;
1183
+                $sanitized_limit[] = (int) $limit_part;
1184 1184
             }
1185 1185
             $model_query_params['limit'] = implode(',', $sanitized_limit);
1186 1186
         } else {
@@ -1270,17 +1270,17 @@  discard block
 block discarded – undo
1270 1270
         foreach ($exploded_contents as $item) {
1271 1271
             $item = trim($item);
1272 1272
             //if no prefix was provided, so we look for items with no "." in them
1273
-            if (! $prefix) {
1273
+            if ( ! $prefix) {
1274 1274
                 //does this item have a period?
1275 1275
                 if (strpos($item, '.') === false) {
1276 1276
                     //if not, then its what we're looking for
1277 1277
                     $contents_with_prefix[] = $item;
1278 1278
                 }
1279
-            } elseif (strpos($item, $prefix . '.') === 0) {
1279
+            } elseif (strpos($item, $prefix.'.') === 0) {
1280 1280
                 //this item has the prefix and a period, grab it
1281 1281
                 $contents_with_prefix[] = substr(
1282 1282
                     $item,
1283
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1283
+                    strpos($item, $prefix.'.') + strlen($prefix.'.')
1284 1284
                 );
1285 1285
             } elseif ($item === $prefix) {
1286 1286
                 //this item is JUST the prefix
@@ -1320,9 +1320,9 @@  discard block
 block discarded – undo
1320 1320
         if ($model_name) {
1321 1321
             foreach ($includes as $field_to_include) {
1322 1322
                 $field_to_include = trim($field_to_include);
1323
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1323
+                if (strpos($field_to_include, $model_name.'.') === 0) {
1324 1324
                     //found the model name at the exact start
1325
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1325
+                    $field_sans_model_name = str_replace($model_name.'.', '', $field_to_include);
1326 1326
                     $extracted_fields_to_include[] = $field_sans_model_name;
1327 1327
                 } elseif ($field_to_include == $model_name) {
1328 1328
                     $extracted_fields_to_include[] = '*';
@@ -1364,7 +1364,7 @@  discard block
 block discarded – undo
1364 1364
         $restricted_query_params['caps'] = $context;
1365 1365
         $this->setDebugInfo('model query params', $restricted_query_params);
1366 1366
         $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1367
-        if (! empty($model_rows)) {
1367
+        if ( ! empty($model_rows)) {
1368 1368
             return $this->createEntityFromWpdbResult(
1369 1369
                 $model,
1370 1370
                 array_shift($model_rows),
@@ -1374,10 +1374,10 @@  discard block
 block discarded – undo
1374 1374
             //ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1375 1375
             $lowercase_model_name = strtolower($model->get_this_model_name());
1376 1376
             $model_rows_found_sans_restrictions = $model->get_all_wpdb_results($query_params);
1377
-            if (! empty($model_rows_found_sans_restrictions)) {
1377
+            if ( ! empty($model_rows_found_sans_restrictions)) {
1378 1378
                 //you got shafted- it existed but we didn't want to tell you!
1379 1379
                 return new WP_Error(
1380
-                    'rest_user_cannot_' . $context,
1380
+                    'rest_user_cannot_'.$context,
1381 1381
                     sprintf(
1382 1382
                         __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1383 1383
                         $context,
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 2 patches
Indentation   +3141 added lines, -3141 removed lines patch added patch discarded remove patch
@@ -15,3147 +15,3147 @@
 block discarded – undo
15 15
 abstract class EE_Base_Class
16 16
 {
17 17
 
18
-    /**
19
-     * This is an array of the original properties and values provided during construction
20
-     * of this model object. (keys are model field names, values are their values).
21
-     * This list is important to remember so that when we are merging data from the db, we know
22
-     * which values to override and which to not override.
23
-     *
24
-     * @var array
25
-     */
26
-    protected $_props_n_values_provided_in_constructor;
27
-
28
-    /**
29
-     * Timezone
30
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
31
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
32
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
33
-     * access to it.
34
-     *
35
-     * @var string
36
-     */
37
-    protected $_timezone;
38
-
39
-    /**
40
-     * date format
41
-     * pattern or format for displaying dates
42
-     *
43
-     * @var string $_dt_frmt
44
-     */
45
-    protected $_dt_frmt;
46
-
47
-    /**
48
-     * time format
49
-     * pattern or format for displaying time
50
-     *
51
-     * @var string $_tm_frmt
52
-     */
53
-    protected $_tm_frmt;
54
-
55
-    /**
56
-     * This property is for holding a cached array of object properties indexed by property name as the key.
57
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
58
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
59
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
60
-     *
61
-     * @var array
62
-     */
63
-    protected $_cached_properties = array();
64
-
65
-    /**
66
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
67
-     * single
68
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
69
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
70
-     * all others have an array)
71
-     *
72
-     * @var array
73
-     */
74
-    protected $_model_relations = array();
75
-
76
-    /**
77
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
78
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
79
-     *
80
-     * @var array
81
-     */
82
-    protected $_fields = array();
83
-
84
-    /**
85
-     * @var boolean indicating whether or not this model object is intended to ever be saved
86
-     * For example, we might create model objects intended to only be used for the duration
87
-     * of this request and to be thrown away, and if they were accidentally saved
88
-     * it would be a bug.
89
-     */
90
-    protected $_allow_persist = true;
91
-
92
-    /**
93
-     * @var boolean indicating whether or not this model object's properties have changed since construction
94
-     */
95
-    protected $_has_changes = false;
96
-
97
-    /**
98
-     * @var EEM_Base
99
-     */
100
-    protected $_model;
101
-
102
-    /**
103
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
104
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
105
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
106
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
107
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
108
-     * array as:
109
-     * array(
110
-     *  'Registration_Count' => 24
111
-     * );
112
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
113
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
114
-     * info)
115
-     *
116
-     * @var array
117
-     */
118
-    protected $custom_selection_results = array();
119
-
120
-
121
-    /**
122
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
123
-     * play nice
124
-     *
125
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
126
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
127
-     *                                                         TXN_amount, QST_name, etc) and values are their values
128
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
129
-     *                                                         corresponding db model or not.
130
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
131
-     *                                                         be in when instantiating a EE_Base_Class object.
132
-     * @param array   $date_formats                            An array of date formats to set on construct where first
133
-     *                                                         value is the date_format and second value is the time
134
-     *                                                         format.
135
-     * @throws InvalidArgumentException
136
-     * @throws InvalidInterfaceException
137
-     * @throws InvalidDataTypeException
138
-     * @throws EE_Error
139
-     * @throws ReflectionException
140
-     */
141
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
142
-    {
143
-        $className = get_class($this);
144
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
145
-        $model        = $this->get_model();
146
-        $model_fields = $model->field_settings(false);
147
-        // ensure $fieldValues is an array
148
-        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
149
-        // verify client code has not passed any invalid field names
150
-        foreach ($fieldValues as $field_name => $field_value) {
151
-            if (! isset($model_fields[ $field_name ])) {
152
-                throw new EE_Error(
153
-                    sprintf(
154
-                        esc_html__(
155
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
156
-                            'event_espresso'
157
-                        ),
158
-                        $field_name,
159
-                        get_class($this),
160
-                        implode(', ', array_keys($model_fields))
161
-                    )
162
-                );
163
-            }
164
-        }
165
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
166
-        if (! empty($date_formats) && is_array($date_formats)) {
167
-            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
168
-        } else {
169
-            //set default formats for date and time
170
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
171
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
172
-        }
173
-        //if db model is instantiating
174
-        if ($bydb) {
175
-            //client code has indicated these field values are from the database
176
-            foreach ($model_fields as $fieldName => $field) {
177
-                $this->set_from_db(
178
-                    $fieldName,
179
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
180
-                );
181
-            }
182
-        } else {
183
-            //we're constructing a brand
184
-            //new instance of the model object. Generally, this means we'll need to do more field validation
185
-            foreach ($model_fields as $fieldName => $field) {
186
-                $this->set(
187
-                    $fieldName,
188
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null, true
189
-                );
190
-            }
191
-        }
192
-        //remember what values were passed to this constructor
193
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
194
-        //remember in entity mapper
195
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
196
-            $model->add_to_entity_map($this);
197
-        }
198
-        //setup all the relations
199
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
200
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
-                $this->_model_relations[ $relation_name ] = null;
202
-            } else {
203
-                $this->_model_relations[ $relation_name ] = array();
204
-            }
205
-        }
206
-        /**
207
-         * Action done at the end of each model object construction
208
-         *
209
-         * @param EE_Base_Class $this the model object just created
210
-         */
211
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
-    }
213
-
214
-
215
-    /**
216
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
217
-     *
218
-     * @return boolean
219
-     */
220
-    public function allow_persist()
221
-    {
222
-        return $this->_allow_persist;
223
-    }
224
-
225
-
226
-    /**
227
-     * Sets whether or not this model object should be allowed to be saved to the DB.
228
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
229
-     * you got new information that somehow made you change your mind.
230
-     *
231
-     * @param boolean $allow_persist
232
-     * @return boolean
233
-     */
234
-    public function set_allow_persist($allow_persist)
235
-    {
236
-        return $this->_allow_persist = $allow_persist;
237
-    }
238
-
239
-
240
-    /**
241
-     * Gets the field's original value when this object was constructed during this request.
242
-     * This can be helpful when determining if a model object has changed or not
243
-     *
244
-     * @param string $field_name
245
-     * @return mixed|null
246
-     * @throws ReflectionException
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidInterfaceException
249
-     * @throws InvalidDataTypeException
250
-     * @throws EE_Error
251
-     */
252
-    public function get_original($field_name)
253
-    {
254
-        if (isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
256
-        ) {
257
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
-        }
259
-        return null;
260
-    }
261
-
262
-
263
-    /**
264
-     * @param EE_Base_Class $obj
265
-     * @return string
266
-     */
267
-    public function get_class($obj)
268
-    {
269
-        return get_class($obj);
270
-    }
271
-
272
-
273
-    /**
274
-     * Overrides parent because parent expects old models.
275
-     * This also doesn't do any validation, and won't work for serialized arrays
276
-     *
277
-     * @param    string $field_name
278
-     * @param    mixed  $field_value
279
-     * @param bool      $use_default
280
-     * @throws InvalidArgumentException
281
-     * @throws InvalidInterfaceException
282
-     * @throws InvalidDataTypeException
283
-     * @throws EE_Error
284
-     * @throws ReflectionException
285
-     * @throws ReflectionException
286
-     * @throws ReflectionException
287
-     */
288
-    public function set($field_name, $field_value, $use_default = false)
289
-    {
290
-        // if not using default and nothing has changed, and object has already been setup (has ID),
291
-        // then don't do anything
292
-        if (
293
-            ! $use_default
294
-            && $this->_fields[ $field_name ] === $field_value
295
-            && $this->ID()
296
-        ) {
297
-            return;
298
-        }
299
-        $model              = $this->get_model();
300
-        $this->_has_changes = true;
301
-        $field_obj          = $model->field_settings_for($field_name);
302
-        if ($field_obj instanceof EE_Model_Field_Base) {
303
-            //			if ( method_exists( $field_obj, 'set_timezone' )) {
304
-            if ($field_obj instanceof EE_Datetime_Field) {
305
-                $field_obj->set_timezone($this->_timezone);
306
-                $field_obj->set_date_format($this->_dt_frmt);
307
-                $field_obj->set_time_format($this->_tm_frmt);
308
-            }
309
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
310
-            //should the value be null?
311
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
313
-                /**
314
-                 * To save having to refactor all the models, if a default value is used for a
315
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
-                 * object.
318
-                 *
319
-                 * @since 4.6.10+
320
-                 */
321
-                if (
322
-                    $field_obj instanceof EE_Datetime_Field
323
-                    && $this->_fields[ $field_name ] !== null
324
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
325
-                ) {
326
-                    empty($this->_fields[ $field_name ])
327
-                        ? $this->set($field_name, time())
328
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
329
-                }
330
-            } else {
331
-                $this->_fields[ $field_name ] = $holder_of_value;
332
-            }
333
-            //if we're not in the constructor...
334
-            //now check if what we set was a primary key
335
-            if (
336
-                //note: props_n_values_provided_in_constructor is only set at the END of the constructor
337
-                $this->_props_n_values_provided_in_constructor
338
-                && $field_value
339
-                && $field_name === $model->primary_key_name()
340
-            ) {
341
-                //if so, we want all this object's fields to be filled either with
342
-                //what we've explicitly set on this model
343
-                //or what we have in the db
344
-                // echo "setting primary key!";
345
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
346
-                $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
-                foreach ($fields_on_model as $field_obj) {
348
-                    if (! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
349
-                        && $field_obj->get_name() !== $field_name
350
-                    ) {
351
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
352
-                    }
353
-                }
354
-                //oh this model object has an ID? well make sure its in the entity mapper
355
-                $model->add_to_entity_map($this);
356
-            }
357
-            //let's unset any cache for this field_name from the $_cached_properties property.
358
-            $this->_clear_cached_property($field_name);
359
-        } else {
360
-            throw new EE_Error(
361
-                sprintf(
362
-                    esc_html__(
363
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
364
-                        'event_espresso'
365
-                    ),
366
-                    $field_name
367
-                )
368
-            );
369
-        }
370
-    }
371
-
372
-
373
-    /**
374
-     * Set custom select values for model.
375
-     *
376
-     * @param array $custom_select_values
377
-     */
378
-    public function setCustomSelectsValues(array $custom_select_values)
379
-    {
380
-        $this->custom_selection_results = $custom_select_values;
381
-    }
382
-
383
-
384
-    /**
385
-     * Returns the custom select value for the provided alias if its set.
386
-     * If not set, returns null.
387
-     *
388
-     * @param string $alias
389
-     * @return string|int|float|null
390
-     */
391
-    public function getCustomSelect($alias)
392
-    {
393
-        return isset($this->custom_selection_results[ $alias ])
394
-            ? $this->custom_selection_results[ $alias ]
395
-            : null;
396
-    }
397
-
398
-
399
-    /**
400
-     * This sets the field value on the db column if it exists for the given $column_name or
401
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
402
-     *
403
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
404
-     * @param string $field_name  Must be the exact column name.
405
-     * @param mixed  $field_value The value to set.
406
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
407
-     * @throws InvalidArgumentException
408
-     * @throws InvalidInterfaceException
409
-     * @throws InvalidDataTypeException
410
-     * @throws EE_Error
411
-     * @throws ReflectionException
412
-     */
413
-    public function set_field_or_extra_meta($field_name, $field_value)
414
-    {
415
-        if ($this->get_model()->has_field($field_name)) {
416
-            $this->set($field_name, $field_value);
417
-            return true;
418
-        }
419
-        //ensure this object is saved first so that extra meta can be properly related.
420
-        $this->save();
421
-        return $this->update_extra_meta($field_name, $field_value);
422
-    }
423
-
424
-
425
-    /**
426
-     * This retrieves the value of the db column set on this class or if that's not present
427
-     * it will attempt to retrieve from extra_meta if found.
428
-     * Example Usage:
429
-     * Via EE_Message child class:
430
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
431
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
432
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
433
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
434
-     * value for those extra fields dynamically via the EE_message object.
435
-     *
436
-     * @param  string $field_name expecting the fully qualified field name.
437
-     * @return mixed|null  value for the field if found.  null if not found.
438
-     * @throws ReflectionException
439
-     * @throws InvalidArgumentException
440
-     * @throws InvalidInterfaceException
441
-     * @throws InvalidDataTypeException
442
-     * @throws EE_Error
443
-     */
444
-    public function get_field_or_extra_meta($field_name)
445
-    {
446
-        if ($this->get_model()->has_field($field_name)) {
447
-            $column_value = $this->get($field_name);
448
-        } else {
449
-            //This isn't a column in the main table, let's see if it is in the extra meta.
450
-            $column_value = $this->get_extra_meta($field_name, true, null);
451
-        }
452
-        return $column_value;
453
-    }
454
-
455
-
456
-    /**
457
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
458
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
459
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
460
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
461
-     *
462
-     * @access public
463
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
464
-     * @return void
465
-     * @throws InvalidArgumentException
466
-     * @throws InvalidInterfaceException
467
-     * @throws InvalidDataTypeException
468
-     * @throws EE_Error
469
-     * @throws ReflectionException
470
-     */
471
-    public function set_timezone($timezone = '')
472
-    {
473
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
474
-        //make sure we clear all cached properties because they won't be relevant now
475
-        $this->_clear_cached_properties();
476
-        //make sure we update field settings and the date for all EE_Datetime_Fields
477
-        $model_fields = $this->get_model()->field_settings(false);
478
-        foreach ($model_fields as $field_name => $field_obj) {
479
-            if ($field_obj instanceof EE_Datetime_Field) {
480
-                $field_obj->set_timezone($this->_timezone);
481
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
482
-                    EEH_DTT_Helper::setTimezone($this->_fields[$field_name], new DateTimeZone($this->_timezone));
483
-                }
484
-            }
485
-        }
486
-    }
487
-
488
-
489
-    /**
490
-     * This just returns whatever is set for the current timezone.
491
-     *
492
-     * @access public
493
-     * @return string timezone string
494
-     */
495
-    public function get_timezone()
496
-    {
497
-        return $this->_timezone;
498
-    }
499
-
500
-
501
-    /**
502
-     * This sets the internal date format to what is sent in to be used as the new default for the class
503
-     * internally instead of wp set date format options
504
-     *
505
-     * @since 4.6
506
-     * @param string $format should be a format recognizable by PHP date() functions.
507
-     */
508
-    public function set_date_format($format)
509
-    {
510
-        $this->_dt_frmt = $format;
511
-        //clear cached_properties because they won't be relevant now.
512
-        $this->_clear_cached_properties();
513
-    }
514
-
515
-
516
-    /**
517
-     * This sets the internal time format string to what is sent in to be used as the new default for the
518
-     * class internally instead of wp set time format options.
519
-     *
520
-     * @since 4.6
521
-     * @param string $format should be a format recognizable by PHP date() functions.
522
-     */
523
-    public function set_time_format($format)
524
-    {
525
-        $this->_tm_frmt = $format;
526
-        //clear cached_properties because they won't be relevant now.
527
-        $this->_clear_cached_properties();
528
-    }
529
-
530
-
531
-    /**
532
-     * This returns the current internal set format for the date and time formats.
533
-     *
534
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
535
-     *                             where the first value is the date format and the second value is the time format.
536
-     * @return mixed string|array
537
-     */
538
-    public function get_format($full = true)
539
-    {
540
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
541
-    }
542
-
543
-
544
-    /**
545
-     * cache
546
-     * stores the passed model object on the current model object.
547
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
548
-     *
549
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
550
-     *                                       'Registration' associated with this model object
551
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
552
-     *                                       that could be a payment or a registration)
553
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
554
-     *                                       items which will be stored in an array on this object
555
-     * @throws ReflectionException
556
-     * @throws InvalidArgumentException
557
-     * @throws InvalidInterfaceException
558
-     * @throws InvalidDataTypeException
559
-     * @throws EE_Error
560
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
561
-     *                                       related thing, no array)
562
-     */
563
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
564
-    {
565
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
566
-        if (! $object_to_cache instanceof EE_Base_Class) {
567
-            return false;
568
-        }
569
-        // also get "how" the object is related, or throw an error
570
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
571
-            throw new EE_Error(
572
-                sprintf(
573
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
574
-                    $relationName,
575
-                    get_class($this)
576
-                )
577
-            );
578
-        }
579
-        // how many things are related ?
580
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
581
-            // if it's a "belongs to" relationship, then there's only one related model object
582
-            // eg, if this is a registration, there's only 1 attendee for it
583
-            // so for these model objects just set it to be cached
584
-            $this->_model_relations[ $relationName ] = $object_to_cache;
585
-            $return                                  = true;
586
-        } else {
587
-            // otherwise, this is the "many" side of a one to many relationship,
588
-            // so we'll add the object to the array of related objects for that type.
589
-            // eg: if this is an event, there are many registrations for that event,
590
-            // so we cache the registrations in an array
591
-            if (! is_array($this->_model_relations[ $relationName ])) {
592
-                // if for some reason, the cached item is a model object,
593
-                // then stick that in the array, otherwise start with an empty array
594
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
595
-                                                           instanceof
596
-                                                           EE_Base_Class
597
-                    ? array($this->_model_relations[ $relationName ]) : array();
598
-            }
599
-            // first check for a cache_id which is normally empty
600
-            if (! empty($cache_id)) {
601
-                // if the cache_id exists, then it means we are purposely trying to cache this
602
-                // with a known key that can then be used to retrieve the object later on
603
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
604
-                $return                                               = $cache_id;
605
-            } elseif ($object_to_cache->ID()) {
606
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
607
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
608
-                $return                                                            = $object_to_cache->ID();
609
-            } else {
610
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
611
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
612
-                // move the internal pointer to the end of the array
613
-                end($this->_model_relations[ $relationName ]);
614
-                // and grab the key so that we can return it
615
-                $return = key($this->_model_relations[ $relationName ]);
616
-            }
617
-        }
618
-        return $return;
619
-    }
620
-
621
-
622
-    /**
623
-     * For adding an item to the cached_properties property.
624
-     *
625
-     * @access protected
626
-     * @param string      $fieldname the property item the corresponding value is for.
627
-     * @param mixed       $value     The value we are caching.
628
-     * @param string|null $cache_type
629
-     * @return void
630
-     * @throws ReflectionException
631
-     * @throws InvalidArgumentException
632
-     * @throws InvalidInterfaceException
633
-     * @throws InvalidDataTypeException
634
-     * @throws EE_Error
635
-     */
636
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
637
-    {
638
-        //first make sure this property exists
639
-        $this->get_model()->field_settings_for($fieldname);
640
-        $cache_type                                            = empty($cache_type) ? 'standard' : $cache_type;
641
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
642
-    }
643
-
644
-
645
-    /**
646
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
647
-     * This also SETS the cache if we return the actual property!
648
-     *
649
-     * @param string $fieldname        the name of the property we're trying to retrieve
650
-     * @param bool   $pretty
651
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
652
-     *                                 (in cases where the same property may be used for different outputs
653
-     *                                 - i.e. datetime, money etc.)
654
-     *                                 It can also accept certain pre-defined "schema" strings
655
-     *                                 to define how to output the property.
656
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
657
-     * @return mixed                   whatever the value for the property is we're retrieving
658
-     * @throws ReflectionException
659
-     * @throws InvalidArgumentException
660
-     * @throws InvalidInterfaceException
661
-     * @throws InvalidDataTypeException
662
-     * @throws EE_Error
663
-     */
664
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
665
-    {
666
-        //verify the field exists
667
-        $model = $this->get_model();
668
-        $model->field_settings_for($fieldname);
669
-        $cache_type = $pretty ? 'pretty' : 'standard';
670
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
671
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
672
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
673
-        }
674
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
675
-        $this->_set_cached_property($fieldname, $value, $cache_type);
676
-        return $value;
677
-    }
678
-
679
-
680
-    /**
681
-     * If the cache didn't fetch the needed item, this fetches it.
682
-     *
683
-     * @param string $fieldname
684
-     * @param bool   $pretty
685
-     * @param string $extra_cache_ref
686
-     * @return mixed
687
-     * @throws InvalidArgumentException
688
-     * @throws InvalidInterfaceException
689
-     * @throws InvalidDataTypeException
690
-     * @throws EE_Error
691
-     * @throws ReflectionException
692
-     */
693
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
694
-    {
695
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
696
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
697
-        if ($field_obj instanceof EE_Datetime_Field) {
698
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
699
-        }
700
-        if (! isset($this->_fields[ $fieldname ])) {
701
-            $this->_fields[ $fieldname ] = null;
702
-        }
703
-        $value = $pretty
704
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
705
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
706
-        return $value;
707
-    }
708
-
709
-
710
-    /**
711
-     * set timezone, formats, and output for EE_Datetime_Field objects
712
-     *
713
-     * @param \EE_Datetime_Field $datetime_field
714
-     * @param bool               $pretty
715
-     * @param null               $date_or_time
716
-     * @return void
717
-     * @throws InvalidArgumentException
718
-     * @throws InvalidInterfaceException
719
-     * @throws InvalidDataTypeException
720
-     * @throws EE_Error
721
-     */
722
-    protected function _prepare_datetime_field(
723
-        EE_Datetime_Field $datetime_field,
724
-        $pretty = false,
725
-        $date_or_time = null
726
-    ) {
727
-        $datetime_field->set_timezone($this->_timezone);
728
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
729
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
730
-        //set the output returned
731
-        switch ($date_or_time) {
732
-            case 'D' :
733
-                $datetime_field->set_date_time_output('date');
734
-                break;
735
-            case 'T' :
736
-                $datetime_field->set_date_time_output('time');
737
-                break;
738
-            default :
739
-                $datetime_field->set_date_time_output();
740
-        }
741
-    }
742
-
743
-
744
-    /**
745
-     * This just takes care of clearing out the cached_properties
746
-     *
747
-     * @return void
748
-     */
749
-    protected function _clear_cached_properties()
750
-    {
751
-        $this->_cached_properties = array();
752
-    }
753
-
754
-
755
-    /**
756
-     * This just clears out ONE property if it exists in the cache
757
-     *
758
-     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
759
-     * @return void
760
-     */
761
-    protected function _clear_cached_property($property_name)
762
-    {
763
-        if (isset($this->_cached_properties[ $property_name ])) {
764
-            unset($this->_cached_properties[ $property_name ]);
765
-        }
766
-    }
767
-
768
-
769
-    /**
770
-     * Ensures that this related thing is a model object.
771
-     *
772
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
773
-     * @param string $model_name   name of the related thing, eg 'Attendee',
774
-     * @return EE_Base_Class
775
-     * @throws ReflectionException
776
-     * @throws InvalidArgumentException
777
-     * @throws InvalidInterfaceException
778
-     * @throws InvalidDataTypeException
779
-     * @throws EE_Error
780
-     */
781
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
782
-    {
783
-        $other_model_instance = self::_get_model_instance_with_name(
784
-            self::_get_model_classname($model_name),
785
-            $this->_timezone
786
-        );
787
-        return $other_model_instance->ensure_is_obj($object_or_id);
788
-    }
789
-
790
-
791
-    /**
792
-     * Forgets the cached model of the given relation Name. So the next time we request it,
793
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
794
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
795
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
796
-     *
797
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
798
-     *                                                     Eg 'Registration'
799
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
800
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
801
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
802
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
803
-     *                                                     this is HasMany or HABTM.
804
-     * @throws ReflectionException
805
-     * @throws InvalidArgumentException
806
-     * @throws InvalidInterfaceException
807
-     * @throws InvalidDataTypeException
808
-     * @throws EE_Error
809
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
810
-     *                                                     relation from all
811
-     */
812
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
813
-    {
814
-        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
815
-        $index_in_cache        = '';
816
-        if (! $relationship_to_model) {
817
-            throw new EE_Error(
818
-                sprintf(
819
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
820
-                    $relationName,
821
-                    get_class($this)
822
-                )
823
-            );
824
-        }
825
-        if ($clear_all) {
826
-            $obj_removed                             = true;
827
-            $this->_model_relations[ $relationName ] = null;
828
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
829
-            $obj_removed                             = $this->_model_relations[ $relationName ];
830
-            $this->_model_relations[ $relationName ] = null;
831
-        } else {
832
-            if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
833
-                && $object_to_remove_or_index_into_array->ID()
834
-            ) {
835
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
836
-                if (is_array($this->_model_relations[ $relationName ])
837
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
838
-                ) {
839
-                    $index_found_at = null;
840
-                    //find this object in the array even though it has a different key
841
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
842
-                        /** @noinspection TypeUnsafeComparisonInspection */
843
-                        if (
844
-                            $obj instanceof EE_Base_Class
845
-                            && (
846
-                                $obj == $object_to_remove_or_index_into_array
847
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
848
-                            )
849
-                        ) {
850
-                            $index_found_at = $index;
851
-                            break;
852
-                        }
853
-                    }
854
-                    if ($index_found_at) {
855
-                        $index_in_cache = $index_found_at;
856
-                    } else {
857
-                        //it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
858
-                        //if it wasn't in it to begin with. So we're done
859
-                        return $object_to_remove_or_index_into_array;
860
-                    }
861
-                }
862
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
863
-                //so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
864
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
865
-                    /** @noinspection TypeUnsafeComparisonInspection */
866
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
867
-                        $index_in_cache = $index;
868
-                    }
869
-                }
870
-            } else {
871
-                $index_in_cache = $object_to_remove_or_index_into_array;
872
-            }
873
-            //supposedly we've found it. But it could just be that the client code
874
-            //provided a bad index/object
875
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
876
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
877
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
878
-            } else {
879
-                //that thing was never cached anyways.
880
-                $obj_removed = null;
881
-            }
882
-        }
883
-        return $obj_removed;
884
-    }
885
-
886
-
887
-    /**
888
-     * update_cache_after_object_save
889
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
890
-     * obtained after being saved to the db
891
-     *
892
-     * @param string        $relationName       - the type of object that is cached
893
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
894
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
895
-     * @return boolean TRUE on success, FALSE on fail
896
-     * @throws ReflectionException
897
-     * @throws InvalidArgumentException
898
-     * @throws InvalidInterfaceException
899
-     * @throws InvalidDataTypeException
900
-     * @throws EE_Error
901
-     */
902
-    public function update_cache_after_object_save(
903
-        $relationName,
904
-        EE_Base_Class $newly_saved_object,
905
-        $current_cache_id = ''
906
-    ) {
907
-        // verify that incoming object is of the correct type
908
-        $obj_class = 'EE_' . $relationName;
909
-        if ($newly_saved_object instanceof $obj_class) {
910
-            /* @type EE_Base_Class $newly_saved_object */
911
-            // now get the type of relation
912
-            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
913
-            // if this is a 1:1 relationship
914
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
915
-                // then just replace the cached object with the newly saved object
916
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
917
-                return true;
918
-                // or if it's some kind of sordid feral polyamorous relationship...
919
-            }
920
-            if (is_array($this->_model_relations[ $relationName ])
921
-                      && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
922
-            ) {
923
-                // then remove the current cached item
924
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
925
-                // and cache the newly saved object using it's new ID
926
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
927
-                return true;
928
-            }
929
-        }
930
-        return false;
931
-    }
932
-
933
-
934
-    /**
935
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
936
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
937
-     *
938
-     * @param string $relationName
939
-     * @return EE_Base_Class
940
-     */
941
-    public function get_one_from_cache($relationName)
942
-    {
943
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
944
-            ? $this->_model_relations[ $relationName ]
945
-            : null;
946
-        if (is_array($cached_array_or_object)) {
947
-            return array_shift($cached_array_or_object);
948
-        }
949
-        return $cached_array_or_object;
950
-    }
951
-
952
-
953
-    /**
954
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
955
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
956
-     *
957
-     * @param string $relationName
958
-     * @throws ReflectionException
959
-     * @throws InvalidArgumentException
960
-     * @throws InvalidInterfaceException
961
-     * @throws InvalidDataTypeException
962
-     * @throws EE_Error
963
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
964
-     */
965
-    public function get_all_from_cache($relationName)
966
-    {
967
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
968
-        // if the result is not an array, but exists, make it an array
969
-        $objects = is_array($objects) ? $objects : array($objects);
970
-        //bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
971
-        //basically, if this model object was stored in the session, and these cached model objects
972
-        //already have IDs, let's make sure they're in their model's entity mapper
973
-        //otherwise we will have duplicates next time we call
974
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
975
-        $model = EE_Registry::instance()->load_model($relationName);
976
-        foreach ($objects as $model_object) {
977
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
978
-                //ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
979
-                if ($model_object->ID()) {
980
-                    $model->add_to_entity_map($model_object);
981
-                }
982
-            } else {
983
-                throw new EE_Error(
984
-                    sprintf(
985
-                        esc_html__(
986
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
987
-                            'event_espresso'
988
-                        ),
989
-                        $relationName,
990
-                        gettype($model_object)
991
-                    )
992
-                );
993
-            }
994
-        }
995
-        return $objects;
996
-    }
997
-
998
-
999
-    /**
1000
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1001
-     * matching the given query conditions.
1002
-     *
1003
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1004
-     * @param int   $limit              How many objects to return.
1005
-     * @param array $query_params       Any additional conditions on the query.
1006
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1007
-     *                                  you can indicate just the columns you want returned
1008
-     * @return array|EE_Base_Class[]
1009
-     * @throws ReflectionException
1010
-     * @throws InvalidArgumentException
1011
-     * @throws InvalidInterfaceException
1012
-     * @throws InvalidDataTypeException
1013
-     * @throws EE_Error
1014
-     */
1015
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1016
-    {
1017
-        $model         = $this->get_model();
1018
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1019
-            ? $model->get_primary_key_field()->get_name()
1020
-            : $field_to_order_by;
1021
-        $current_value = ! empty($field) ? $this->get($field) : null;
1022
-        if (empty($field) || empty($current_value)) {
1023
-            return array();
1024
-        }
1025
-        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1026
-    }
1027
-
1028
-
1029
-    /**
1030
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1031
-     * matching the given query conditions.
1032
-     *
1033
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1034
-     * @param int   $limit              How many objects to return.
1035
-     * @param array $query_params       Any additional conditions on the query.
1036
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1037
-     *                                  you can indicate just the columns you want returned
1038
-     * @return array|EE_Base_Class[]
1039
-     * @throws ReflectionException
1040
-     * @throws InvalidArgumentException
1041
-     * @throws InvalidInterfaceException
1042
-     * @throws InvalidDataTypeException
1043
-     * @throws EE_Error
1044
-     */
1045
-    public function previous_x(
1046
-        $field_to_order_by = null,
1047
-        $limit = 1,
1048
-        $query_params = array(),
1049
-        $columns_to_select = null
1050
-    ) {
1051
-        $model         = $this->get_model();
1052
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1053
-            ? $model->get_primary_key_field()->get_name()
1054
-            : $field_to_order_by;
1055
-        $current_value = ! empty($field) ? $this->get($field) : null;
1056
-        if (empty($field) || empty($current_value)) {
1057
-            return array();
1058
-        }
1059
-        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1060
-    }
1061
-
1062
-
1063
-    /**
1064
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1065
-     * matching the given query conditions.
1066
-     *
1067
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1068
-     * @param array $query_params       Any additional conditions on the query.
1069
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1070
-     *                                  you can indicate just the columns you want returned
1071
-     * @return array|EE_Base_Class
1072
-     * @throws ReflectionException
1073
-     * @throws InvalidArgumentException
1074
-     * @throws InvalidInterfaceException
1075
-     * @throws InvalidDataTypeException
1076
-     * @throws EE_Error
1077
-     */
1078
-    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1079
-    {
1080
-        $model         = $this->get_model();
1081
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1082
-            ? $model->get_primary_key_field()->get_name()
1083
-            : $field_to_order_by;
1084
-        $current_value = ! empty($field) ? $this->get($field) : null;
1085
-        if (empty($field) || empty($current_value)) {
1086
-            return array();
1087
-        }
1088
-        return $model->next($current_value, $field, $query_params, $columns_to_select);
1089
-    }
1090
-
1091
-
1092
-    /**
1093
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1094
-     * matching the given query conditions.
1095
-     *
1096
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1097
-     * @param array $query_params       Any additional conditions on the query.
1098
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1099
-     *                                  you can indicate just the column you want returned
1100
-     * @return array|EE_Base_Class
1101
-     * @throws ReflectionException
1102
-     * @throws InvalidArgumentException
1103
-     * @throws InvalidInterfaceException
1104
-     * @throws InvalidDataTypeException
1105
-     * @throws EE_Error
1106
-     */
1107
-    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1108
-    {
1109
-        $model         = $this->get_model();
1110
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1111
-            ? $model->get_primary_key_field()->get_name()
1112
-            : $field_to_order_by;
1113
-        $current_value = ! empty($field) ? $this->get($field) : null;
1114
-        if (empty($field) || empty($current_value)) {
1115
-            return array();
1116
-        }
1117
-        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1118
-    }
1119
-
1120
-
1121
-    /**
1122
-     * Overrides parent because parent expects old models.
1123
-     * This also doesn't do any validation, and won't work for serialized arrays
1124
-     *
1125
-     * @param string $field_name
1126
-     * @param mixed  $field_value_from_db
1127
-     * @throws ReflectionException
1128
-     * @throws InvalidArgumentException
1129
-     * @throws InvalidInterfaceException
1130
-     * @throws InvalidDataTypeException
1131
-     * @throws EE_Error
1132
-     */
1133
-    public function set_from_db($field_name, $field_value_from_db)
1134
-    {
1135
-        $field_obj = $this->get_model()->field_settings_for($field_name);
1136
-        if ($field_obj instanceof EE_Model_Field_Base) {
1137
-            //you would think the DB has no NULLs for non-null label fields right? wrong!
1138
-            //eg, a CPT model object could have an entry in the posts table, but no
1139
-            //entry in the meta table. Meaning that all its columns in the meta table
1140
-            //are null! yikes! so when we find one like that, use defaults for its meta columns
1141
-            if ($field_value_from_db === null) {
1142
-                if ($field_obj->is_nullable()) {
1143
-                    //if the field allows nulls, then let it be null
1144
-                    $field_value = null;
1145
-                } else {
1146
-                    $field_value = $field_obj->get_default_value();
1147
-                }
1148
-            } else {
1149
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1150
-            }
1151
-            $this->_fields[ $field_name ] = $field_value;
1152
-            $this->_clear_cached_property($field_name);
1153
-        }
1154
-    }
1155
-
1156
-
1157
-    /**
1158
-     * verifies that the specified field is of the correct type
1159
-     *
1160
-     * @param string $field_name
1161
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1162
-     *                                (in cases where the same property may be used for different outputs
1163
-     *                                - i.e. datetime, money etc.)
1164
-     * @return mixed
1165
-     * @throws ReflectionException
1166
-     * @throws InvalidArgumentException
1167
-     * @throws InvalidInterfaceException
1168
-     * @throws InvalidDataTypeException
1169
-     * @throws EE_Error
1170
-     */
1171
-    public function get($field_name, $extra_cache_ref = null)
1172
-    {
1173
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1174
-    }
1175
-
1176
-
1177
-    /**
1178
-     * This method simply returns the RAW unprocessed value for the given property in this class
1179
-     *
1180
-     * @param  string $field_name A valid fieldname
1181
-     * @return mixed              Whatever the raw value stored on the property is.
1182
-     * @throws ReflectionException
1183
-     * @throws InvalidArgumentException
1184
-     * @throws InvalidInterfaceException
1185
-     * @throws InvalidDataTypeException
1186
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1187
-     */
1188
-    public function get_raw($field_name)
1189
-    {
1190
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1191
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1192
-            ? $this->_fields[ $field_name ]->format('U')
1193
-            : $this->_fields[ $field_name ];
1194
-    }
1195
-
1196
-
1197
-    /**
1198
-     * This is used to return the internal DateTime object used for a field that is a
1199
-     * EE_Datetime_Field.
1200
-     *
1201
-     * @param string $field_name               The field name retrieving the DateTime object.
1202
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1203
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1204
-     *                                         EE_Datetime_Field and but the field value is null, then
1205
-     *                                         just null is returned (because that indicates that likely
1206
-     *                                         this field is nullable).
1207
-     * @throws InvalidArgumentException
1208
-     * @throws InvalidDataTypeException
1209
-     * @throws InvalidInterfaceException
1210
-     * @throws ReflectionException
1211
-     */
1212
-    public function get_DateTime_object($field_name)
1213
-    {
1214
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1215
-        if (! $field_settings instanceof EE_Datetime_Field) {
1216
-            EE_Error::add_error(
1217
-                sprintf(
1218
-                    esc_html__(
1219
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1220
-                        'event_espresso'
1221
-                    ),
1222
-                    $field_name
1223
-                ),
1224
-                __FILE__,
1225
-                __FUNCTION__,
1226
-                __LINE__
1227
-            );
1228
-            return false;
1229
-        }
1230
-        return isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime
1231
-            ? clone $this->_fields[$field_name]
1232
-            : null;
1233
-    }
1234
-
1235
-
1236
-    /**
1237
-     * To be used in template to immediately echo out the value, and format it for output.
1238
-     * Eg, should call stripslashes and whatnot before echoing
1239
-     *
1240
-     * @param string $field_name      the name of the field as it appears in the DB
1241
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1242
-     *                                (in cases where the same property may be used for different outputs
1243
-     *                                - i.e. datetime, money etc.)
1244
-     * @return void
1245
-     * @throws ReflectionException
1246
-     * @throws InvalidArgumentException
1247
-     * @throws InvalidInterfaceException
1248
-     * @throws InvalidDataTypeException
1249
-     * @throws EE_Error
1250
-     */
1251
-    public function e($field_name, $extra_cache_ref = null)
1252
-    {
1253
-        echo $this->get_pretty($field_name, $extra_cache_ref);
1254
-    }
1255
-
1256
-
1257
-    /**
1258
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1259
-     * can be easily used as the value of form input.
1260
-     *
1261
-     * @param string $field_name
1262
-     * @return void
1263
-     * @throws ReflectionException
1264
-     * @throws InvalidArgumentException
1265
-     * @throws InvalidInterfaceException
1266
-     * @throws InvalidDataTypeException
1267
-     * @throws EE_Error
1268
-     */
1269
-    public function f($field_name)
1270
-    {
1271
-        $this->e($field_name, 'form_input');
1272
-    }
1273
-
1274
-
1275
-    /**
1276
-     * Same as `f()` but just returns the value instead of echoing it
1277
-     *
1278
-     * @param string $field_name
1279
-     * @return string
1280
-     * @throws ReflectionException
1281
-     * @throws InvalidArgumentException
1282
-     * @throws InvalidInterfaceException
1283
-     * @throws InvalidDataTypeException
1284
-     * @throws EE_Error
1285
-     */
1286
-    public function get_f($field_name)
1287
-    {
1288
-        return (string) $this->get_pretty($field_name, 'form_input');
1289
-    }
1290
-
1291
-
1292
-    /**
1293
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1294
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1295
-     * to see what options are available.
1296
-     *
1297
-     * @param string $field_name
1298
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1299
-     *                                (in cases where the same property may be used for different outputs
1300
-     *                                - i.e. datetime, money etc.)
1301
-     * @return mixed
1302
-     * @throws ReflectionException
1303
-     * @throws InvalidArgumentException
1304
-     * @throws InvalidInterfaceException
1305
-     * @throws InvalidDataTypeException
1306
-     * @throws EE_Error
1307
-     */
1308
-    public function get_pretty($field_name, $extra_cache_ref = null)
1309
-    {
1310
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1311
-    }
1312
-
1313
-
1314
-    /**
1315
-     * This simply returns the datetime for the given field name
1316
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1317
-     * (and the equivalent e_date, e_time, e_datetime).
1318
-     *
1319
-     * @access   protected
1320
-     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1321
-     * @param string   $dt_frmt      valid datetime format used for date
1322
-     *                               (if '' then we just use the default on the field,
1323
-     *                               if NULL we use the last-used format)
1324
-     * @param string   $tm_frmt      Same as above except this is for time format
1325
-     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1326
-     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1327
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1328
-     *                               if field is not a valid dtt field, or void if echoing
1329
-     * @throws ReflectionException
1330
-     * @throws InvalidArgumentException
1331
-     * @throws InvalidInterfaceException
1332
-     * @throws InvalidDataTypeException
1333
-     * @throws EE_Error
1334
-     */
1335
-    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1336
-    {
1337
-        // clear cached property
1338
-        $this->_clear_cached_property($field_name);
1339
-        //reset format properties because they are used in get()
1340
-        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1341
-        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1342
-        if ($echo) {
1343
-            $this->e($field_name, $date_or_time);
1344
-            return '';
1345
-        }
1346
-        return $this->get($field_name, $date_or_time);
1347
-    }
1348
-
1349
-
1350
-    /**
1351
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1352
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1353
-     * other echoes the pretty value for dtt)
1354
-     *
1355
-     * @param  string $field_name name of model object datetime field holding the value
1356
-     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1357
-     * @return string            datetime value formatted
1358
-     * @throws ReflectionException
1359
-     * @throws InvalidArgumentException
1360
-     * @throws InvalidInterfaceException
1361
-     * @throws InvalidDataTypeException
1362
-     * @throws EE_Error
1363
-     */
1364
-    public function get_date($field_name, $format = '')
1365
-    {
1366
-        return $this->_get_datetime($field_name, $format, null, 'D');
1367
-    }
1368
-
1369
-
1370
-    /**
1371
-     * @param        $field_name
1372
-     * @param string $format
1373
-     * @throws ReflectionException
1374
-     * @throws InvalidArgumentException
1375
-     * @throws InvalidInterfaceException
1376
-     * @throws InvalidDataTypeException
1377
-     * @throws EE_Error
1378
-     */
1379
-    public function e_date($field_name, $format = '')
1380
-    {
1381
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1382
-    }
1383
-
1384
-
1385
-    /**
1386
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1387
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1388
-     * other echoes the pretty value for dtt)
1389
-     *
1390
-     * @param  string $field_name name of model object datetime field holding the value
1391
-     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1392
-     * @return string             datetime value formatted
1393
-     * @throws ReflectionException
1394
-     * @throws InvalidArgumentException
1395
-     * @throws InvalidInterfaceException
1396
-     * @throws InvalidDataTypeException
1397
-     * @throws EE_Error
1398
-     */
1399
-    public function get_time($field_name, $format = '')
1400
-    {
1401
-        return $this->_get_datetime($field_name, null, $format, 'T');
1402
-    }
1403
-
1404
-
1405
-    /**
1406
-     * @param        $field_name
1407
-     * @param string $format
1408
-     * @throws ReflectionException
1409
-     * @throws InvalidArgumentException
1410
-     * @throws InvalidInterfaceException
1411
-     * @throws InvalidDataTypeException
1412
-     * @throws EE_Error
1413
-     */
1414
-    public function e_time($field_name, $format = '')
1415
-    {
1416
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1417
-    }
1418
-
1419
-
1420
-    /**
1421
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1422
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1423
-     * other echoes the pretty value for dtt)
1424
-     *
1425
-     * @param  string $field_name name of model object datetime field holding the value
1426
-     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1427
-     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1428
-     * @return string             datetime value formatted
1429
-     * @throws ReflectionException
1430
-     * @throws InvalidArgumentException
1431
-     * @throws InvalidInterfaceException
1432
-     * @throws InvalidDataTypeException
1433
-     * @throws EE_Error
1434
-     */
1435
-    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1436
-    {
1437
-        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1438
-    }
1439
-
1440
-
1441
-    /**
1442
-     * @param string $field_name
1443
-     * @param string $dt_frmt
1444
-     * @param string $tm_frmt
1445
-     * @throws ReflectionException
1446
-     * @throws InvalidArgumentException
1447
-     * @throws InvalidInterfaceException
1448
-     * @throws InvalidDataTypeException
1449
-     * @throws EE_Error
1450
-     */
1451
-    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1452
-    {
1453
-        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1454
-    }
1455
-
1456
-
1457
-    /**
1458
-     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1459
-     *
1460
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1461
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1462
-     *                           on the object will be used.
1463
-     * @return string Date and time string in set locale or false if no field exists for the given
1464
-     * @throws ReflectionException
1465
-     * @throws InvalidArgumentException
1466
-     * @throws InvalidInterfaceException
1467
-     * @throws InvalidDataTypeException
1468
-     * @throws EE_Error
1469
-     *                           field name.
1470
-     */
1471
-    public function get_i18n_datetime($field_name, $format = '')
1472
-    {
1473
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1474
-        return date_i18n(
1475
-            $format,
1476
-            EEH_DTT_Helper::get_timestamp_with_offset(
1477
-                $this->get_raw($field_name),
1478
-                $this->_timezone
1479
-            )
1480
-        );
1481
-    }
1482
-
1483
-
1484
-    /**
1485
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1486
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1487
-     * thrown.
1488
-     *
1489
-     * @param  string $field_name The field name being checked
1490
-     * @throws ReflectionException
1491
-     * @throws InvalidArgumentException
1492
-     * @throws InvalidInterfaceException
1493
-     * @throws InvalidDataTypeException
1494
-     * @throws EE_Error
1495
-     * @return EE_Datetime_Field
1496
-     */
1497
-    protected function _get_dtt_field_settings($field_name)
1498
-    {
1499
-        $field = $this->get_model()->field_settings_for($field_name);
1500
-        //check if field is dtt
1501
-        if ($field instanceof EE_Datetime_Field) {
1502
-            return $field;
1503
-        }
1504
-        throw new EE_Error(
1505
-            sprintf(
1506
-                esc_html__(
1507
-                    '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',
1508
-                    'event_espresso'
1509
-                ),
1510
-                $field_name,
1511
-                self::_get_model_classname(get_class($this))
1512
-            )
1513
-        );
1514
-    }
1515
-
1516
-
1517
-
1518
-
1519
-    /**
1520
-     * NOTE ABOUT BELOW:
1521
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1522
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1523
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1524
-     * method and make sure you send the entire datetime value for setting.
1525
-     */
1526
-    /**
1527
-     * sets the time on a datetime property
1528
-     *
1529
-     * @access protected
1530
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1531
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1532
-     * @throws ReflectionException
1533
-     * @throws InvalidArgumentException
1534
-     * @throws InvalidInterfaceException
1535
-     * @throws InvalidDataTypeException
1536
-     * @throws EE_Error
1537
-     */
1538
-    protected function _set_time_for($time, $fieldname)
1539
-    {
1540
-        $this->_set_date_time('T', $time, $fieldname);
1541
-    }
1542
-
1543
-
1544
-    /**
1545
-     * sets the date on a datetime property
1546
-     *
1547
-     * @access protected
1548
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1549
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1550
-     * @throws ReflectionException
1551
-     * @throws InvalidArgumentException
1552
-     * @throws InvalidInterfaceException
1553
-     * @throws InvalidDataTypeException
1554
-     * @throws EE_Error
1555
-     */
1556
-    protected function _set_date_for($date, $fieldname)
1557
-    {
1558
-        $this->_set_date_time('D', $date, $fieldname);
1559
-    }
1560
-
1561
-
1562
-    /**
1563
-     * This takes care of setting a date or time independently on a given model object property. This method also
1564
-     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1565
-     *
1566
-     * @access protected
1567
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1568
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1569
-     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1570
-     *                                        EE_Datetime_Field property)
1571
-     * @throws ReflectionException
1572
-     * @throws InvalidArgumentException
1573
-     * @throws InvalidInterfaceException
1574
-     * @throws InvalidDataTypeException
1575
-     * @throws EE_Error
1576
-     */
1577
-    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1578
-    {
1579
-        $field = $this->_get_dtt_field_settings($fieldname);
1580
-        $field->set_timezone($this->_timezone);
1581
-        $field->set_date_format($this->_dt_frmt);
1582
-        $field->set_time_format($this->_tm_frmt);
1583
-        switch ($what) {
1584
-            case 'T' :
1585
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1586
-                    $datetime_value,
1587
-                    $this->_fields[ $fieldname ]
1588
-                );
1589
-                break;
1590
-            case 'D' :
1591
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1592
-                    $datetime_value,
1593
-                    $this->_fields[ $fieldname ]
1594
-                );
1595
-                break;
1596
-            case 'B' :
1597
-                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1598
-                break;
1599
-        }
1600
-        $this->_clear_cached_property($fieldname);
1601
-    }
1602
-
1603
-
1604
-    /**
1605
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1606
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1607
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1608
-     * that could lead to some unexpected results!
1609
-     *
1610
-     * @access public
1611
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1612
-     *                                         value being returned.
1613
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1614
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1615
-     * @param string $prepend                  You can include something to prepend on the timestamp
1616
-     * @param string $append                   You can include something to append on the timestamp
1617
-     * @throws ReflectionException
1618
-     * @throws InvalidArgumentException
1619
-     * @throws InvalidInterfaceException
1620
-     * @throws InvalidDataTypeException
1621
-     * @throws EE_Error
1622
-     * @return string timestamp
1623
-     */
1624
-    public function display_in_my_timezone(
1625
-        $field_name,
1626
-        $callback = 'get_datetime',
1627
-        $args = null,
1628
-        $prepend = '',
1629
-        $append = ''
1630
-    ) {
1631
-        $timezone = EEH_DTT_Helper::get_timezone();
1632
-        if ($timezone === $this->_timezone) {
1633
-            return '';
1634
-        }
1635
-        $original_timezone = $this->_timezone;
1636
-        $this->set_timezone($timezone);
1637
-        $fn   = (array) $field_name;
1638
-        $args = array_merge($fn, (array) $args);
1639
-        if (! method_exists($this, $callback)) {
1640
-            throw new EE_Error(
1641
-                sprintf(
1642
-                    esc_html__(
1643
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1644
-                        'event_espresso'
1645
-                    ),
1646
-                    $callback
1647
-                )
1648
-            );
1649
-        }
1650
-        $args   = (array) $args;
1651
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1652
-        $this->set_timezone($original_timezone);
1653
-        return $return;
1654
-    }
1655
-
1656
-
1657
-    /**
1658
-     * Deletes this model object.
1659
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1660
-     * override
1661
-     * `EE_Base_Class::_delete` NOT this class.
1662
-     *
1663
-     * @return boolean | int
1664
-     * @throws ReflectionException
1665
-     * @throws InvalidArgumentException
1666
-     * @throws InvalidInterfaceException
1667
-     * @throws InvalidDataTypeException
1668
-     * @throws EE_Error
1669
-     */
1670
-    public function delete()
1671
-    {
1672
-        /**
1673
-         * Called just before the `EE_Base_Class::_delete` method call.
1674
-         * Note:
1675
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1676
-         * should be aware that `_delete` may not always result in a permanent delete.
1677
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1678
-         * soft deletes (trash) the object and does not permanently delete it.
1679
-         *
1680
-         * @param EE_Base_Class $model_object about to be 'deleted'
1681
-         */
1682
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1683
-        $result = $this->_delete();
1684
-        /**
1685
-         * Called just after the `EE_Base_Class::_delete` method call.
1686
-         * Note:
1687
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1688
-         * should be aware that `_delete` may not always result in a permanent delete.
1689
-         * For example `EE_Soft_Base_Class::_delete`
1690
-         * soft deletes (trash) the object and does not permanently delete it.
1691
-         *
1692
-         * @param EE_Base_Class $model_object that was just 'deleted'
1693
-         * @param boolean       $result
1694
-         */
1695
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1696
-        return $result;
1697
-    }
1698
-
1699
-
1700
-    /**
1701
-     * Calls the specific delete method for the instantiated class.
1702
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1703
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1704
-     * `EE_Base_Class::delete`
1705
-     *
1706
-     * @return bool|int
1707
-     * @throws ReflectionException
1708
-     * @throws InvalidArgumentException
1709
-     * @throws InvalidInterfaceException
1710
-     * @throws InvalidDataTypeException
1711
-     * @throws EE_Error
1712
-     */
1713
-    protected function _delete()
1714
-    {
1715
-        return $this->delete_permanently();
1716
-    }
1717
-
1718
-
1719
-    /**
1720
-     * Deletes this model object permanently from db
1721
-     * (but keep in mind related models may block the delete and return an error)
1722
-     *
1723
-     * @return bool | int
1724
-     * @throws ReflectionException
1725
-     * @throws InvalidArgumentException
1726
-     * @throws InvalidInterfaceException
1727
-     * @throws InvalidDataTypeException
1728
-     * @throws EE_Error
1729
-     */
1730
-    public function delete_permanently()
1731
-    {
1732
-        /**
1733
-         * Called just before HARD deleting a model object
1734
-         *
1735
-         * @param EE_Base_Class $model_object about to be 'deleted'
1736
-         */
1737
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1738
-        $model  = $this->get_model();
1739
-        $result = $model->delete_permanently_by_ID($this->ID());
1740
-        $this->refresh_cache_of_related_objects();
1741
-        /**
1742
-         * Called just after HARD deleting a model object
1743
-         *
1744
-         * @param EE_Base_Class $model_object that was just 'deleted'
1745
-         * @param boolean       $result
1746
-         */
1747
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1748
-        return $result;
1749
-    }
1750
-
1751
-
1752
-    /**
1753
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1754
-     * related model objects
1755
-     *
1756
-     * @throws ReflectionException
1757
-     * @throws InvalidArgumentException
1758
-     * @throws InvalidInterfaceException
1759
-     * @throws InvalidDataTypeException
1760
-     * @throws EE_Error
1761
-     */
1762
-    public function refresh_cache_of_related_objects()
1763
-    {
1764
-        $model = $this->get_model();
1765
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1766
-            if (! empty($this->_model_relations[ $relation_name ])) {
1767
-                $related_objects = $this->_model_relations[ $relation_name ];
1768
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1769
-                    //this relation only stores a single model object, not an array
1770
-                    //but let's make it consistent
1771
-                    $related_objects = array($related_objects);
1772
-                }
1773
-                foreach ($related_objects as $related_object) {
1774
-                    //only refresh their cache if they're in memory
1775
-                    if ($related_object instanceof EE_Base_Class) {
1776
-                        $related_object->clear_cache(
1777
-                            $model->get_this_model_name(),
1778
-                            $this
1779
-                        );
1780
-                    }
1781
-                }
1782
-            }
1783
-        }
1784
-    }
1785
-
1786
-
1787
-    /**
1788
-     *        Saves this object to the database. An array may be supplied to set some values on this
1789
-     * object just before saving.
1790
-     *
1791
-     * @access public
1792
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1793
-     *                                 if provided during the save() method (often client code will change the fields'
1794
-     *                                 values before calling save)
1795
-     * @throws InvalidArgumentException
1796
-     * @throws InvalidInterfaceException
1797
-     * @throws InvalidDataTypeException
1798
-     * @throws EE_Error
1799
-     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1800
-     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1801
-     * @throws ReflectionException
1802
-     * @throws ReflectionException
1803
-     * @throws ReflectionException
1804
-     */
1805
-    public function save($set_cols_n_values = array())
1806
-    {
1807
-        $model = $this->get_model();
1808
-        /**
1809
-         * Filters the fields we're about to save on the model object
1810
-         *
1811
-         * @param array         $set_cols_n_values
1812
-         * @param EE_Base_Class $model_object
1813
-         */
1814
-        $set_cols_n_values = (array) apply_filters(
1815
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1816
-            $set_cols_n_values,
1817
-            $this
1818
-        );
1819
-        //set attributes as provided in $set_cols_n_values
1820
-        foreach ($set_cols_n_values as $column => $value) {
1821
-            $this->set($column, $value);
1822
-        }
1823
-        // no changes ? then don't do anything
1824
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1825
-            return 0;
1826
-        }
1827
-        /**
1828
-         * Saving a model object.
1829
-         * Before we perform a save, this action is fired.
1830
-         *
1831
-         * @param EE_Base_Class $model_object the model object about to be saved.
1832
-         */
1833
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1834
-        if (! $this->allow_persist()) {
1835
-            return 0;
1836
-        }
1837
-        // now get current attribute values
1838
-        $save_cols_n_values = $this->_fields;
1839
-        // if the object already has an ID, update it. Otherwise, insert it
1840
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1841
-        // They have been
1842
-        $old_assumption_concerning_value_preparation = $model
1843
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1844
-        $model->assume_values_already_prepared_by_model_object(true);
1845
-        //does this model have an autoincrement PK?
1846
-        if ($model->has_primary_key_field()) {
1847
-            if ($model->get_primary_key_field()->is_auto_increment()) {
1848
-                //ok check if it's set, if so: update; if not, insert
1849
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1850
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1851
-                } else {
1852
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1853
-                    $results = $model->insert($save_cols_n_values);
1854
-                    if ($results) {
1855
-                        //if successful, set the primary key
1856
-                        //but don't use the normal SET method, because it will check if
1857
-                        //an item with the same ID exists in the mapper & db, then
1858
-                        //will find it in the db (because we just added it) and THAT object
1859
-                        //will get added to the mapper before we can add this one!
1860
-                        //but if we just avoid using the SET method, all that headache can be avoided
1861
-                        $pk_field_name                   = $model->primary_key_name();
1862
-                        $this->_fields[ $pk_field_name ] = $results;
1863
-                        $this->_clear_cached_property($pk_field_name);
1864
-                        $model->add_to_entity_map($this);
1865
-                        $this->_update_cached_related_model_objs_fks();
1866
-                    }
1867
-                }
1868
-            } else {//PK is NOT auto-increment
1869
-                //so check if one like it already exists in the db
1870
-                if ($model->exists_by_ID($this->ID())) {
1871
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1872
-                        throw new EE_Error(
1873
-                            sprintf(
1874
-                                esc_html__(
1875
-                                    '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',
1876
-                                    'event_espresso'
1877
-                                ),
1878
-                                get_class($this),
1879
-                                get_class($model) . '::instance()->add_to_entity_map()',
1880
-                                get_class($model) . '::instance()->get_one_by_ID()',
1881
-                                '<br />'
1882
-                            )
1883
-                        );
1884
-                    }
1885
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1886
-                } else {
1887
-                    $results = $model->insert($save_cols_n_values);
1888
-                    $this->_update_cached_related_model_objs_fks();
1889
-                }
1890
-            }
1891
-        } else {//there is NO primary key
1892
-            $already_in_db = false;
1893
-            foreach ($model->unique_indexes() as $index) {
1894
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1895
-                if ($model->exists(array($uniqueness_where_params))) {
1896
-                    $already_in_db = true;
1897
-                }
1898
-            }
1899
-            if ($already_in_db) {
1900
-                $combined_pk_fields_n_values = array_intersect_key($save_cols_n_values,
1901
-                    $model->get_combined_primary_key_fields());
1902
-                $results                     = $model->update(
1903
-                    $save_cols_n_values,
1904
-                    $combined_pk_fields_n_values
1905
-                );
1906
-            } else {
1907
-                $results = $model->insert($save_cols_n_values);
1908
-            }
1909
-        }
1910
-        //restore the old assumption about values being prepared by the model object
1911
-        $model->assume_values_already_prepared_by_model_object(
1912
-                $old_assumption_concerning_value_preparation
1913
-            );
1914
-        /**
1915
-         * After saving the model object this action is called
1916
-         *
1917
-         * @param EE_Base_Class $model_object which was just saved
1918
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1919
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1920
-         */
1921
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1922
-        $this->_has_changes = false;
1923
-        return $results;
1924
-    }
1925
-
1926
-
1927
-    /**
1928
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1929
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1930
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1931
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1932
-     * 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
1933
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1934
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1935
-     *
1936
-     * @return void
1937
-     * @throws ReflectionException
1938
-     * @throws InvalidArgumentException
1939
-     * @throws InvalidInterfaceException
1940
-     * @throws InvalidDataTypeException
1941
-     * @throws EE_Error
1942
-     */
1943
-    protected function _update_cached_related_model_objs_fks()
1944
-    {
1945
-        $model = $this->get_model();
1946
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1947
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1948
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1949
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1950
-                        $model->get_this_model_name()
1951
-                    );
1952
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1953
-                    if ($related_model_obj_in_cache->ID()) {
1954
-                        $related_model_obj_in_cache->save();
1955
-                    }
1956
-                }
1957
-            }
1958
-        }
1959
-    }
1960
-
1961
-
1962
-    /**
1963
-     * Saves this model object and its NEW cached relations to the database.
1964
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1965
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1966
-     * because otherwise, there's a potential for infinite looping of saving
1967
-     * Saves the cached related model objects, and ensures the relation between them
1968
-     * and this object and properly setup
1969
-     *
1970
-     * @return int ID of new model object on save; 0 on failure+
1971
-     * @throws ReflectionException
1972
-     * @throws InvalidArgumentException
1973
-     * @throws InvalidInterfaceException
1974
-     * @throws InvalidDataTypeException
1975
-     * @throws EE_Error
1976
-     */
1977
-    public function save_new_cached_related_model_objs()
1978
-    {
1979
-        //make sure this has been saved
1980
-        if (! $this->ID()) {
1981
-            $id = $this->save();
1982
-        } else {
1983
-            $id = $this->ID();
1984
-        }
1985
-        //now save all the NEW cached model objects  (ie they don't exist in the DB)
1986
-        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1987
-            if ($this->_model_relations[ $relationName ]) {
1988
-                //is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1989
-                //or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1990
-                /* @var $related_model_obj EE_Base_Class */
1991
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
1992
-                    //add a relation to that relation type (which saves the appropriate thing in the process)
1993
-                    //but ONLY if it DOES NOT exist in the DB
1994
-                    $related_model_obj = $this->_model_relations[ $relationName ];
1995
-                    //					if( ! $related_model_obj->ID()){
1996
-                    $this->_add_relation_to($related_model_obj, $relationName);
1997
-                    $related_model_obj->save_new_cached_related_model_objs();
1998
-                    //					}
1999
-                } else {
2000
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2001
-                        //add a relation to that relation type (which saves the appropriate thing in the process)
2002
-                        //but ONLY if it DOES NOT exist in the DB
2003
-                        //						if( ! $related_model_obj->ID()){
2004
-                        $this->_add_relation_to($related_model_obj, $relationName);
2005
-                        $related_model_obj->save_new_cached_related_model_objs();
2006
-                        //						}
2007
-                    }
2008
-                }
2009
-            }
2010
-        }
2011
-        return $id;
2012
-    }
2013
-
2014
-
2015
-    /**
2016
-     * for getting a model while instantiated.
2017
-     *
2018
-     * @return EEM_Base | EEM_CPT_Base
2019
-     * @throws ReflectionException
2020
-     * @throws InvalidArgumentException
2021
-     * @throws InvalidInterfaceException
2022
-     * @throws InvalidDataTypeException
2023
-     * @throws EE_Error
2024
-     */
2025
-    public function get_model()
2026
-    {
2027
-        if (! $this->_model) {
2028
-            $modelName    = self::_get_model_classname(get_class($this));
2029
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2030
-        } else {
2031
-            $this->_model->set_timezone($this->_timezone);
2032
-        }
2033
-        return $this->_model;
2034
-    }
2035
-
2036
-
2037
-    /**
2038
-     * @param $props_n_values
2039
-     * @param $classname
2040
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2041
-     * @throws ReflectionException
2042
-     * @throws InvalidArgumentException
2043
-     * @throws InvalidInterfaceException
2044
-     * @throws InvalidDataTypeException
2045
-     * @throws EE_Error
2046
-     */
2047
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2048
-    {
2049
-        //TODO: will not work for Term_Relationships because they have no PK!
2050
-        $primary_id_ref = self::_get_primary_key_name($classname);
2051
-        if (
2052
-            array_key_exists($primary_id_ref, $props_n_values)
2053
-            && ! empty($props_n_values[ $primary_id_ref ])
2054
-        ) {
2055
-            $id = $props_n_values[ $primary_id_ref ];
2056
-            return self::_get_model($classname)->get_from_entity_map($id);
2057
-        }
2058
-        return false;
2059
-    }
2060
-
2061
-
2062
-    /**
2063
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2064
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2065
-     * 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
2066
-     * we return false.
2067
-     *
2068
-     * @param  array  $props_n_values   incoming array of properties and their values
2069
-     * @param  string $classname        the classname of the child class
2070
-     * @param null    $timezone
2071
-     * @param array   $date_formats     incoming date_formats in an array where the first value is the
2072
-     *                                  date_format and the second value is the time format
2073
-     * @return mixed (EE_Base_Class|bool)
2074
-     * @throws InvalidArgumentException
2075
-     * @throws InvalidInterfaceException
2076
-     * @throws InvalidDataTypeException
2077
-     * @throws EE_Error
2078
-     * @throws ReflectionException
2079
-     * @throws ReflectionException
2080
-     * @throws ReflectionException
2081
-     */
2082
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2083
-    {
2084
-        $existing = null;
2085
-        $model    = self::_get_model($classname, $timezone);
2086
-        if ($model->has_primary_key_field()) {
2087
-            $primary_id_ref = self::_get_primary_key_name($classname);
2088
-            if (array_key_exists($primary_id_ref, $props_n_values)
2089
-                && ! empty($props_n_values[ $primary_id_ref ])
2090
-            ) {
2091
-                $existing = $model->get_one_by_ID(
2092
-                    $props_n_values[ $primary_id_ref ]
2093
-                );
2094
-            }
2095
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2096
-            //no primary key on this model, but there's still a matching item in the DB
2097
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2098
-                self::_get_model($classname, $timezone)
2099
-                    ->get_index_primary_key_string($props_n_values)
2100
-            );
2101
-        }
2102
-        if ($existing) {
2103
-            //set date formats if present before setting values
2104
-            if (! empty($date_formats) && is_array($date_formats)) {
2105
-                $existing->set_date_format($date_formats[0]);
2106
-                $existing->set_time_format($date_formats[1]);
2107
-            } else {
2108
-                //set default formats for date and time
2109
-                $existing->set_date_format(get_option('date_format'));
2110
-                $existing->set_time_format(get_option('time_format'));
2111
-            }
2112
-            foreach ($props_n_values as $property => $field_value) {
2113
-                $existing->set($property, $field_value);
2114
-            }
2115
-            return $existing;
2116
-        }
2117
-        return false;
2118
-    }
2119
-
2120
-
2121
-    /**
2122
-     * Gets the EEM_*_Model for this class
2123
-     *
2124
-     * @access public now, as this is more convenient
2125
-     * @param      $classname
2126
-     * @param null $timezone
2127
-     * @throws ReflectionException
2128
-     * @throws InvalidArgumentException
2129
-     * @throws InvalidInterfaceException
2130
-     * @throws InvalidDataTypeException
2131
-     * @throws EE_Error
2132
-     * @return EEM_Base
2133
-     */
2134
-    protected static function _get_model($classname, $timezone = null)
2135
-    {
2136
-        //find model for this class
2137
-        if (! $classname) {
2138
-            throw new EE_Error(
2139
-                sprintf(
2140
-                    esc_html__(
2141
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2142
-                        'event_espresso'
2143
-                    ),
2144
-                    $classname
2145
-                )
2146
-            );
2147
-        }
2148
-        $modelName = self::_get_model_classname($classname);
2149
-        return self::_get_model_instance_with_name($modelName, $timezone);
2150
-    }
2151
-
2152
-
2153
-    /**
2154
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2155
-     *
2156
-     * @param string $model_classname
2157
-     * @param null   $timezone
2158
-     * @return EEM_Base
2159
-     * @throws ReflectionException
2160
-     * @throws InvalidArgumentException
2161
-     * @throws InvalidInterfaceException
2162
-     * @throws InvalidDataTypeException
2163
-     * @throws EE_Error
2164
-     */
2165
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2166
-    {
2167
-        $model_classname = str_replace('EEM_', '', $model_classname);
2168
-        $model           = EE_Registry::instance()->load_model($model_classname);
2169
-        $model->set_timezone($timezone);
2170
-        return $model;
2171
-    }
2172
-
2173
-
2174
-    /**
2175
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2176
-     * Also works if a model class's classname is provided (eg EE_Registration).
2177
-     *
2178
-     * @param null $model_name
2179
-     * @return string like EEM_Attendee
2180
-     */
2181
-    private static function _get_model_classname($model_name = null)
2182
-    {
2183
-        if (strpos($model_name, 'EE_') === 0) {
2184
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2185
-        } else {
2186
-            $model_classname = 'EEM_' . $model_name;
2187
-        }
2188
-        return $model_classname;
2189
-    }
2190
-
2191
-
2192
-    /**
2193
-     * returns the name of the primary key attribute
2194
-     *
2195
-     * @param null $classname
2196
-     * @throws ReflectionException
2197
-     * @throws InvalidArgumentException
2198
-     * @throws InvalidInterfaceException
2199
-     * @throws InvalidDataTypeException
2200
-     * @throws EE_Error
2201
-     * @return string
2202
-     */
2203
-    protected static function _get_primary_key_name($classname = null)
2204
-    {
2205
-        if (! $classname) {
2206
-            throw new EE_Error(
2207
-                sprintf(
2208
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2209
-                    $classname
2210
-                )
2211
-            );
2212
-        }
2213
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2214
-    }
2215
-
2216
-
2217
-    /**
2218
-     * Gets the value of the primary key.
2219
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2220
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2221
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2222
-     *
2223
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2224
-     * @throws ReflectionException
2225
-     * @throws InvalidArgumentException
2226
-     * @throws InvalidInterfaceException
2227
-     * @throws InvalidDataTypeException
2228
-     * @throws EE_Error
2229
-     */
2230
-    public function ID()
2231
-    {
2232
-        $model = $this->get_model();
2233
-        //now that we know the name of the variable, use a variable variable to get its value and return its
2234
-        if ($model->has_primary_key_field()) {
2235
-            return $this->_fields[ $model->primary_key_name() ];
2236
-        }
2237
-        return $model->get_index_primary_key_string($this->_fields);
2238
-    }
2239
-
2240
-
2241
-    /**
2242
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2243
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2244
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2245
-     *
2246
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2247
-     * @param string $relationName                     eg 'Events','Question',etc.
2248
-     *                                                 an attendee to a group, you also want to specify which role they
2249
-     *                                                 will have in that group. So you would use this parameter to
2250
-     *                                                 specify array('role-column-name'=>'role-id')
2251
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2252
-     *                                                 allow you to further constrict the relation to being added.
2253
-     *                                                 However, keep in mind that the columns (keys) given must match a
2254
-     *                                                 column on the JOIN table and currently only the HABTM models
2255
-     *                                                 accept these additional conditions.  Also remember that if an
2256
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2257
-     *                                                 NEW row is created in the join table.
2258
-     * @param null   $cache_id
2259
-     * @throws ReflectionException
2260
-     * @throws InvalidArgumentException
2261
-     * @throws InvalidInterfaceException
2262
-     * @throws InvalidDataTypeException
2263
-     * @throws EE_Error
2264
-     * @return EE_Base_Class the object the relation was added to
2265
-     */
2266
-    public function _add_relation_to(
2267
-        $otherObjectModelObjectOrID,
2268
-        $relationName,
2269
-        $extra_join_model_fields_n_values = array(),
2270
-        $cache_id = null
2271
-    ) {
2272
-        $model = $this->get_model();
2273
-        //if this thing exists in the DB, save the relation to the DB
2274
-        if ($this->ID()) {
2275
-            $otherObject = $model->add_relationship_to(
2276
-                $this,
2277
-                $otherObjectModelObjectOrID,
2278
-                $relationName,
2279
-                $extra_join_model_fields_n_values
2280
-            );
2281
-            //clear cache so future get_many_related and get_first_related() return new results.
2282
-            $this->clear_cache($relationName, $otherObject, true);
2283
-            if ($otherObject instanceof EE_Base_Class) {
2284
-                $otherObject->clear_cache($model->get_this_model_name(), $this);
2285
-            }
2286
-        } else {
2287
-            //this thing doesn't exist in the DB,  so just cache it
2288
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2289
-                throw new EE_Error(
2290
-                    sprintf(
2291
-                        esc_html__(
2292
-                            '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',
2293
-                            'event_espresso'
2294
-                        ),
2295
-                        $otherObjectModelObjectOrID,
2296
-                        get_class($this)
2297
-                    )
2298
-                );
2299
-            }
2300
-            $otherObject = $otherObjectModelObjectOrID;
2301
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2302
-        }
2303
-        if ($otherObject instanceof EE_Base_Class) {
2304
-            //fix the reciprocal relation too
2305
-            if ($otherObject->ID()) {
2306
-                //its saved so assumed relations exist in the DB, so we can just
2307
-                //clear the cache so future queries use the updated info in the DB
2308
-                $otherObject->clear_cache(
2309
-                    $model->get_this_model_name(),
2310
-                    null,
2311
-                    true
2312
-                );
2313
-            } else {
2314
-                //it's not saved, so it caches relations like this
2315
-                $otherObject->cache($model->get_this_model_name(), $this);
2316
-            }
2317
-        }
2318
-        return $otherObject;
2319
-    }
2320
-
2321
-
2322
-    /**
2323
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2324
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2325
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2326
-     * from the cache
2327
-     *
2328
-     * @param mixed  $otherObjectModelObjectOrID
2329
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2330
-     *                to the DB yet
2331
-     * @param string $relationName
2332
-     * @param array  $where_query
2333
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2334
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2335
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2336
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2337
-     *                created in the join table.
2338
-     * @return EE_Base_Class the relation was removed from
2339
-     * @throws ReflectionException
2340
-     * @throws InvalidArgumentException
2341
-     * @throws InvalidInterfaceException
2342
-     * @throws InvalidDataTypeException
2343
-     * @throws EE_Error
2344
-     */
2345
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2346
-    {
2347
-        if ($this->ID()) {
2348
-            //if this exists in the DB, save the relation change to the DB too
2349
-            $otherObject = $this->get_model()->remove_relationship_to(
2350
-                $this,
2351
-                $otherObjectModelObjectOrID,
2352
-                $relationName,
2353
-                $where_query
2354
-            );
2355
-            $this->clear_cache(
2356
-                $relationName,
2357
-                $otherObject
2358
-            );
2359
-        } else {
2360
-            //this doesn't exist in the DB, just remove it from the cache
2361
-            $otherObject = $this->clear_cache(
2362
-                $relationName,
2363
-                $otherObjectModelObjectOrID
2364
-            );
2365
-        }
2366
-        if ($otherObject instanceof EE_Base_Class) {
2367
-            $otherObject->clear_cache(
2368
-                $this->get_model()->get_this_model_name(),
2369
-                $this
2370
-            );
2371
-        }
2372
-        return $otherObject;
2373
-    }
2374
-
2375
-
2376
-    /**
2377
-     * Removes ALL the related things for the $relationName.
2378
-     *
2379
-     * @param string $relationName
2380
-     * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
2381
-     * @return EE_Base_Class
2382
-     * @throws ReflectionException
2383
-     * @throws InvalidArgumentException
2384
-     * @throws InvalidInterfaceException
2385
-     * @throws InvalidDataTypeException
2386
-     * @throws EE_Error
2387
-     */
2388
-    public function _remove_relations($relationName, $where_query_params = array())
2389
-    {
2390
-        if ($this->ID()) {
2391
-            //if this exists in the DB, save the relation change to the DB too
2392
-            $otherObjects = $this->get_model()->remove_relations(
2393
-                $this,
2394
-                $relationName,
2395
-                $where_query_params
2396
-            );
2397
-            $this->clear_cache(
2398
-                $relationName,
2399
-                null,
2400
-                true
2401
-            );
2402
-        } else {
2403
-            //this doesn't exist in the DB, just remove it from the cache
2404
-            $otherObjects = $this->clear_cache(
2405
-                $relationName,
2406
-                null,
2407
-                true
2408
-            );
2409
-        }
2410
-        if (is_array($otherObjects)) {
2411
-            foreach ($otherObjects as $otherObject) {
2412
-                $otherObject->clear_cache(
2413
-                    $this->get_model()->get_this_model_name(),
2414
-                    $this
2415
-                );
2416
-            }
2417
-        }
2418
-        return $otherObjects;
2419
-    }
2420
-
2421
-
2422
-    /**
2423
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2424
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2425
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2426
-     * because we want to get even deleted items etc.
2427
-     *
2428
-     * @param string $relationName key in the model's _model_relations array
2429
-     * @param array  $query_params like EEM_Base::get_all
2430
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2431
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2432
-     *                             results if you want IDs
2433
-     * @throws ReflectionException
2434
-     * @throws InvalidArgumentException
2435
-     * @throws InvalidInterfaceException
2436
-     * @throws InvalidDataTypeException
2437
-     * @throws EE_Error
2438
-     */
2439
-    public function get_many_related($relationName, $query_params = array())
2440
-    {
2441
-        if ($this->ID()) {
2442
-            //this exists in the DB, so get the related things from either the cache or the DB
2443
-            //if there are query parameters, forget about caching the related model objects.
2444
-            if ($query_params) {
2445
-                $related_model_objects = $this->get_model()->get_all_related(
2446
-                    $this,
2447
-                    $relationName,
2448
-                    $query_params
2449
-                );
2450
-            } else {
2451
-                //did we already cache the result of this query?
2452
-                $cached_results = $this->get_all_from_cache($relationName);
2453
-                if (! $cached_results) {
2454
-                    $related_model_objects = $this->get_model()->get_all_related(
2455
-                        $this,
2456
-                        $relationName,
2457
-                        $query_params
2458
-                    );
2459
-                    //if no query parameters were passed, then we got all the related model objects
2460
-                    //for that relation. We can cache them then.
2461
-                    foreach ($related_model_objects as $related_model_object) {
2462
-                        $this->cache($relationName, $related_model_object);
2463
-                    }
2464
-                } else {
2465
-                    $related_model_objects = $cached_results;
2466
-                }
2467
-            }
2468
-        } else {
2469
-            //this doesn't exist in the DB, so just get the related things from the cache
2470
-            $related_model_objects = $this->get_all_from_cache($relationName);
2471
-        }
2472
-        return $related_model_objects;
2473
-    }
2474
-
2475
-
2476
-    /**
2477
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2478
-     * unless otherwise specified in the $query_params
2479
-     *
2480
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2481
-     * @param array  $query_params   like EEM_Base::get_all's
2482
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2483
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2484
-     *                               that by the setting $distinct to TRUE;
2485
-     * @return int
2486
-     * @throws ReflectionException
2487
-     * @throws InvalidArgumentException
2488
-     * @throws InvalidInterfaceException
2489
-     * @throws InvalidDataTypeException
2490
-     * @throws EE_Error
2491
-     */
2492
-    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2493
-    {
2494
-        return $this->get_model()->count_related(
2495
-            $this,
2496
-            $relation_name,
2497
-            $query_params,
2498
-            $field_to_count,
2499
-            $distinct
2500
-        );
2501
-    }
2502
-
2503
-
2504
-    /**
2505
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2506
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2507
-     *
2508
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2509
-     * @param array  $query_params  like EEM_Base::get_all's
2510
-     * @param string $field_to_sum  name of field to count by.
2511
-     *                              By default, uses primary key
2512
-     *                              (which doesn't make much sense, so you should probably change it)
2513
-     * @return int
2514
-     * @throws ReflectionException
2515
-     * @throws InvalidArgumentException
2516
-     * @throws InvalidInterfaceException
2517
-     * @throws InvalidDataTypeException
2518
-     * @throws EE_Error
2519
-     */
2520
-    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2521
-    {
2522
-        return $this->get_model()->sum_related(
2523
-            $this,
2524
-            $relation_name,
2525
-            $query_params,
2526
-            $field_to_sum
2527
-        );
2528
-    }
2529
-
2530
-
2531
-    /**
2532
-     * Gets the first (ie, one) related model object of the specified type.
2533
-     *
2534
-     * @param string $relationName key in the model's _model_relations array
2535
-     * @param array  $query_params like EEM_Base::get_all
2536
-     * @return EE_Base_Class (not an array, a single object)
2537
-     * @throws ReflectionException
2538
-     * @throws InvalidArgumentException
2539
-     * @throws InvalidInterfaceException
2540
-     * @throws InvalidDataTypeException
2541
-     * @throws EE_Error
2542
-     */
2543
-    public function get_first_related($relationName, $query_params = array())
2544
-    {
2545
-        $model = $this->get_model();
2546
-        if ($this->ID()) {//this exists in the DB, get from the cache OR the DB
2547
-            //if they've provided some query parameters, don't bother trying to cache the result
2548
-            //also make sure we're not caching the result of get_first_related
2549
-            //on a relation which should have an array of objects (because the cache might have an array of objects)
2550
-            if ($query_params
2551
-                || ! $model->related_settings_for($relationName)
2552
-                     instanceof
2553
-                     EE_Belongs_To_Relation
2554
-            ) {
2555
-                $related_model_object = $model->get_first_related(
2556
-                    $this,
2557
-                    $relationName,
2558
-                    $query_params
2559
-                );
2560
-            } else {
2561
-                //first, check if we've already cached the result of this query
2562
-                $cached_result = $this->get_one_from_cache($relationName);
2563
-                if (! $cached_result) {
2564
-                    $related_model_object = $model->get_first_related(
2565
-                        $this,
2566
-                        $relationName,
2567
-                        $query_params
2568
-                    );
2569
-                    $this->cache($relationName, $related_model_object);
2570
-                } else {
2571
-                    $related_model_object = $cached_result;
2572
-                }
2573
-            }
2574
-        } else {
2575
-            $related_model_object = null;
2576
-            // this doesn't exist in the Db,
2577
-            // but maybe the relation is of type belongs to, and so the related thing might
2578
-            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2579
-                $related_model_object = $model->get_first_related(
2580
-                    $this,
2581
-                    $relationName,
2582
-                    $query_params
2583
-                );
2584
-            }
2585
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2586
-            // just get what's cached on this object
2587
-            if (! $related_model_object) {
2588
-                $related_model_object = $this->get_one_from_cache($relationName);
2589
-            }
2590
-        }
2591
-        return $related_model_object;
2592
-    }
2593
-
2594
-
2595
-    /**
2596
-     * Does a delete on all related objects of type $relationName and removes
2597
-     * the current model object's relation to them. If they can't be deleted (because
2598
-     * of blocking related model objects) does nothing. If the related model objects are
2599
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2600
-     * If this model object doesn't exist yet in the DB, just removes its related things
2601
-     *
2602
-     * @param string $relationName
2603
-     * @param array  $query_params like EEM_Base::get_all's
2604
-     * @return int how many deleted
2605
-     * @throws ReflectionException
2606
-     * @throws InvalidArgumentException
2607
-     * @throws InvalidInterfaceException
2608
-     * @throws InvalidDataTypeException
2609
-     * @throws EE_Error
2610
-     */
2611
-    public function delete_related($relationName, $query_params = array())
2612
-    {
2613
-        if ($this->ID()) {
2614
-            $count = $this->get_model()->delete_related(
2615
-                $this,
2616
-                $relationName,
2617
-                $query_params
2618
-            );
2619
-        } else {
2620
-            $count = count($this->get_all_from_cache($relationName));
2621
-            $this->clear_cache($relationName, null, true);
2622
-        }
2623
-        return $count;
2624
-    }
2625
-
2626
-
2627
-    /**
2628
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2629
-     * the current model object's relation to them. If they can't be deleted (because
2630
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2631
-     * If the related thing isn't a soft-deletable model object, this function is identical
2632
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2633
-     *
2634
-     * @param string $relationName
2635
-     * @param array  $query_params like EEM_Base::get_all's
2636
-     * @return int how many deleted (including those soft deleted)
2637
-     * @throws ReflectionException
2638
-     * @throws InvalidArgumentException
2639
-     * @throws InvalidInterfaceException
2640
-     * @throws InvalidDataTypeException
2641
-     * @throws EE_Error
2642
-     */
2643
-    public function delete_related_permanently($relationName, $query_params = array())
2644
-    {
2645
-        if ($this->ID()) {
2646
-            $count = $this->get_model()->delete_related_permanently(
2647
-                $this,
2648
-                $relationName,
2649
-                $query_params
2650
-            );
2651
-        } else {
2652
-            $count = count($this->get_all_from_cache($relationName));
2653
-        }
2654
-        $this->clear_cache($relationName, null, true);
2655
-        return $count;
2656
-    }
2657
-
2658
-
2659
-    /**
2660
-     * is_set
2661
-     * Just a simple utility function children can use for checking if property exists
2662
-     *
2663
-     * @access  public
2664
-     * @param  string $field_name property to check
2665
-     * @return bool                              TRUE if existing,FALSE if not.
2666
-     */
2667
-    public function is_set($field_name)
2668
-    {
2669
-        return isset($this->_fields[ $field_name ]);
2670
-    }
2671
-
2672
-
2673
-    /**
2674
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2675
-     * EE_Error exception if they don't
2676
-     *
2677
-     * @param  mixed (string|array) $properties properties to check
2678
-     * @throws EE_Error
2679
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2680
-     */
2681
-    protected function _property_exists($properties)
2682
-    {
2683
-        foreach ((array) $properties as $property_name) {
2684
-            //first make sure this property exists
2685
-            if (! $this->_fields[ $property_name ]) {
2686
-                throw new EE_Error(
2687
-                    sprintf(
2688
-                        esc_html__(
2689
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2690
-                            'event_espresso'
2691
-                        ),
2692
-                        $property_name
2693
-                    )
2694
-                );
2695
-            }
2696
-        }
2697
-        return true;
2698
-    }
2699
-
2700
-
2701
-    /**
2702
-     * This simply returns an array of model fields for this object
2703
-     *
2704
-     * @return array
2705
-     * @throws ReflectionException
2706
-     * @throws InvalidArgumentException
2707
-     * @throws InvalidInterfaceException
2708
-     * @throws InvalidDataTypeException
2709
-     * @throws EE_Error
2710
-     */
2711
-    public function model_field_array()
2712
-    {
2713
-        $fields     = $this->get_model()->field_settings(false);
2714
-        $properties = array();
2715
-        //remove prepended underscore
2716
-        foreach ($fields as $field_name => $settings) {
2717
-            $properties[ $field_name ] = $this->get($field_name);
2718
-        }
2719
-        return $properties;
2720
-    }
2721
-
2722
-
2723
-    /**
2724
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2725
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2726
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2727
-     * Instead of requiring a plugin to extend the EE_Base_Class
2728
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2729
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2730
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2731
-     * and accepts 2 arguments: the object on which the function was called,
2732
-     * and an array of the original arguments passed to the function.
2733
-     * Whatever their callback function returns will be returned by this function.
2734
-     * Example: in functions.php (or in a plugin):
2735
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2736
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2737
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2738
-     *          return $previousReturnValue.$returnString;
2739
-     *      }
2740
-     * require('EE_Answer.class.php');
2741
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2742
-     * echo $answer->my_callback('monkeys',100);
2743
-     * //will output "you called my_callback! and passed args:monkeys,100"
2744
-     *
2745
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2746
-     * @param array  $args       array of original arguments passed to the function
2747
-     * @throws EE_Error
2748
-     * @return mixed whatever the plugin which calls add_filter decides
2749
-     */
2750
-    public function __call($methodName, $args)
2751
-    {
2752
-        $className = get_class($this);
2753
-        $tagName   = "FHEE__{$className}__{$methodName}";
2754
-        if (! has_filter($tagName)) {
2755
-            throw new EE_Error(
2756
-                sprintf(
2757
-                    esc_html__(
2758
-                        "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;}",
2759
-                        'event_espresso'
2760
-                    ),
2761
-                    $methodName,
2762
-                    $className,
2763
-                    $tagName
2764
-                )
2765
-            );
2766
-        }
2767
-        return apply_filters($tagName, null, $this, $args);
2768
-    }
2769
-
2770
-
2771
-    /**
2772
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2773
-     * A $previous_value can be specified in case there are many meta rows with the same key
2774
-     *
2775
-     * @param string $meta_key
2776
-     * @param mixed  $meta_value
2777
-     * @param mixed  $previous_value
2778
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2779
-     *                  NOTE: if the values haven't changed, returns 0
2780
-     * @throws InvalidArgumentException
2781
-     * @throws InvalidInterfaceException
2782
-     * @throws InvalidDataTypeException
2783
-     * @throws EE_Error
2784
-     * @throws ReflectionException
2785
-     */
2786
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2787
-    {
2788
-        $query_params = array(
2789
-            array(
2790
-                'EXM_key'  => $meta_key,
2791
-                'OBJ_ID'   => $this->ID(),
2792
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2793
-            ),
2794
-        );
2795
-        if ($previous_value !== null) {
2796
-            $query_params[0]['EXM_value'] = $meta_value;
2797
-        }
2798
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2799
-        if (! $existing_rows_like_that) {
2800
-            return $this->add_extra_meta($meta_key, $meta_value);
2801
-        }
2802
-        foreach ($existing_rows_like_that as $existing_row) {
2803
-            $existing_row->save(array('EXM_value' => $meta_value));
2804
-        }
2805
-        return count($existing_rows_like_that);
2806
-    }
2807
-
2808
-
2809
-    /**
2810
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2811
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2812
-     * extra meta row was entered, false if not
2813
-     *
2814
-     * @param string  $meta_key
2815
-     * @param mixed   $meta_value
2816
-     * @param boolean $unique
2817
-     * @return boolean
2818
-     * @throws InvalidArgumentException
2819
-     * @throws InvalidInterfaceException
2820
-     * @throws InvalidDataTypeException
2821
-     * @throws EE_Error
2822
-     * @throws ReflectionException
2823
-     * @throws ReflectionException
2824
-     */
2825
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2826
-    {
2827
-        if ($unique) {
2828
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2829
-                array(
2830
-                    array(
2831
-                        'EXM_key'  => $meta_key,
2832
-                        'OBJ_ID'   => $this->ID(),
2833
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2834
-                    ),
2835
-                )
2836
-            );
2837
-            if ($existing_extra_meta) {
2838
-                return false;
2839
-            }
2840
-        }
2841
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2842
-            array(
2843
-                'EXM_key'   => $meta_key,
2844
-                'EXM_value' => $meta_value,
2845
-                'OBJ_ID'    => $this->ID(),
2846
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2847
-            )
2848
-        );
2849
-        $new_extra_meta->save();
2850
-        return true;
2851
-    }
2852
-
2853
-
2854
-    /**
2855
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2856
-     * is specified, only deletes extra meta records with that value.
2857
-     *
2858
-     * @param string $meta_key
2859
-     * @param mixed  $meta_value
2860
-     * @return int number of extra meta rows deleted
2861
-     * @throws InvalidArgumentException
2862
-     * @throws InvalidInterfaceException
2863
-     * @throws InvalidDataTypeException
2864
-     * @throws EE_Error
2865
-     * @throws ReflectionException
2866
-     */
2867
-    public function delete_extra_meta($meta_key, $meta_value = null)
2868
-    {
2869
-        $query_params = array(
2870
-            array(
2871
-                'EXM_key'  => $meta_key,
2872
-                'OBJ_ID'   => $this->ID(),
2873
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2874
-            ),
2875
-        );
2876
-        if ($meta_value !== null) {
2877
-            $query_params[0]['EXM_value'] = $meta_value;
2878
-        }
2879
-        return EEM_Extra_Meta::instance()->delete($query_params);
2880
-    }
2881
-
2882
-
2883
-    /**
2884
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2885
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2886
-     * You can specify $default is case you haven't found the extra meta
2887
-     *
2888
-     * @param string  $meta_key
2889
-     * @param boolean $single
2890
-     * @param mixed   $default if we don't find anything, what should we return?
2891
-     * @return mixed single value if $single; array if ! $single
2892
-     * @throws ReflectionException
2893
-     * @throws InvalidArgumentException
2894
-     * @throws InvalidInterfaceException
2895
-     * @throws InvalidDataTypeException
2896
-     * @throws EE_Error
2897
-     */
2898
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2899
-    {
2900
-        if ($single) {
2901
-            $result = $this->get_first_related(
2902
-                'Extra_Meta',
2903
-                array(array('EXM_key' => $meta_key))
2904
-            );
2905
-            if ($result instanceof EE_Extra_Meta) {
2906
-                return $result->value();
2907
-            }
2908
-        } else {
2909
-            $results = $this->get_many_related(
2910
-                'Extra_Meta',
2911
-                array(array('EXM_key' => $meta_key))
2912
-            );
2913
-            if ($results) {
2914
-                $values = array();
2915
-                foreach ($results as $result) {
2916
-                    if ($result instanceof EE_Extra_Meta) {
2917
-                        $values[ $result->ID() ] = $result->value();
2918
-                    }
2919
-                }
2920
-                return $values;
2921
-            }
2922
-        }
2923
-        //if nothing discovered yet return default.
2924
-        return apply_filters(
2925
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2926
-            $default,
2927
-            $meta_key,
2928
-            $single,
2929
-            $this
2930
-        );
2931
-    }
2932
-
2933
-
2934
-    /**
2935
-     * Returns a simple array of all the extra meta associated with this model object.
2936
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2937
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2938
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2939
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2940
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2941
-     * finally the extra meta's value as each sub-value. (eg
2942
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2943
-     *
2944
-     * @param boolean $one_of_each_key
2945
-     * @return array
2946
-     * @throws ReflectionException
2947
-     * @throws InvalidArgumentException
2948
-     * @throws InvalidInterfaceException
2949
-     * @throws InvalidDataTypeException
2950
-     * @throws EE_Error
2951
-     */
2952
-    public function all_extra_meta_array($one_of_each_key = true)
2953
-    {
2954
-        $return_array = array();
2955
-        if ($one_of_each_key) {
2956
-            $extra_meta_objs = $this->get_many_related(
2957
-                'Extra_Meta',
2958
-                array('group_by' => 'EXM_key')
2959
-            );
2960
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2961
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2962
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2963
-                }
2964
-            }
2965
-        } else {
2966
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2967
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2968
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2969
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2970
-                        $return_array[ $extra_meta_obj->key() ] = array();
2971
-                    }
2972
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2973
-                }
2974
-            }
2975
-        }
2976
-        return $return_array;
2977
-    }
2978
-
2979
-
2980
-    /**
2981
-     * Gets a pretty nice displayable nice for this model object. Often overridden
2982
-     *
2983
-     * @return string
2984
-     * @throws ReflectionException
2985
-     * @throws InvalidArgumentException
2986
-     * @throws InvalidInterfaceException
2987
-     * @throws InvalidDataTypeException
2988
-     * @throws EE_Error
2989
-     */
2990
-    public function name()
2991
-    {
2992
-        //find a field that's not a text field
2993
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2994
-        if ($field_we_can_use) {
2995
-            return $this->get($field_we_can_use->get_name());
2996
-        }
2997
-        $first_few_properties = $this->model_field_array();
2998
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
2999
-        $name_parts           = array();
3000
-        foreach ($first_few_properties as $name => $value) {
3001
-            $name_parts[] = "$name:$value";
3002
-        }
3003
-        return implode(',', $name_parts);
3004
-    }
3005
-
3006
-
3007
-    /**
3008
-     * in_entity_map
3009
-     * Checks if this model object has been proven to already be in the entity map
3010
-     *
3011
-     * @return boolean
3012
-     * @throws ReflectionException
3013
-     * @throws InvalidArgumentException
3014
-     * @throws InvalidInterfaceException
3015
-     * @throws InvalidDataTypeException
3016
-     * @throws EE_Error
3017
-     */
3018
-    public function in_entity_map()
3019
-    {
3020
-        // well, if we looked, did we find it in the entity map?
3021
-        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3022
-    }
3023
-
3024
-
3025
-    /**
3026
-     * refresh_from_db
3027
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3028
-     *
3029
-     * @throws ReflectionException
3030
-     * @throws InvalidArgumentException
3031
-     * @throws InvalidInterfaceException
3032
-     * @throws InvalidDataTypeException
3033
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3034
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3035
-     */
3036
-    public function refresh_from_db()
3037
-    {
3038
-        if ($this->ID() && $this->in_entity_map()) {
3039
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
3040
-        } else {
3041
-            //if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3042
-            //if it has an ID but it's not in the map, and you're asking me to refresh it
3043
-            //that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3044
-            //absolutely nothing in it for this ID
3045
-            if (WP_DEBUG) {
3046
-                throw new EE_Error(
3047
-                    sprintf(
3048
-                        esc_html__('Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3049
-                            'event_espresso'),
3050
-                        $this->ID(),
3051
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3052
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3053
-                    )
3054
-                );
3055
-            }
3056
-        }
3057
-    }
3058
-
3059
-
3060
-    /**
3061
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3062
-     * (probably a bad assumption they have made, oh well)
3063
-     *
3064
-     * @return string
3065
-     */
3066
-    public function __toString()
3067
-    {
3068
-        try {
3069
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3070
-        } catch (Exception $e) {
3071
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3072
-            return '';
3073
-        }
3074
-    }
3075
-
3076
-
3077
-    /**
3078
-     * Clear related model objects if they're already in the DB, because otherwise when we
3079
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3080
-     * This means if we have made changes to those related model objects, and want to unserialize
3081
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3082
-     * Instead, those related model objects should be directly serialized and stored.
3083
-     * Eg, the following won't work:
3084
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3085
-     * $att = $reg->attendee();
3086
-     * $att->set( 'ATT_fname', 'Dirk' );
3087
-     * update_option( 'my_option', serialize( $reg ) );
3088
-     * //END REQUEST
3089
-     * //START NEXT REQUEST
3090
-     * $reg = get_option( 'my_option' );
3091
-     * $reg->attendee()->save();
3092
-     * And would need to be replace with:
3093
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3094
-     * $att = $reg->attendee();
3095
-     * $att->set( 'ATT_fname', 'Dirk' );
3096
-     * update_option( 'my_option', serialize( $reg ) );
3097
-     * //END REQUEST
3098
-     * //START NEXT REQUEST
3099
-     * $att = get_option( 'my_option' );
3100
-     * $att->save();
3101
-     *
3102
-     * @return array
3103
-     * @throws ReflectionException
3104
-     * @throws InvalidArgumentException
3105
-     * @throws InvalidInterfaceException
3106
-     * @throws InvalidDataTypeException
3107
-     * @throws EE_Error
3108
-     */
3109
-    public function __sleep()
3110
-    {
3111
-        $model = $this->get_model();
3112
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3113
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3114
-                $classname = 'EE_' . $model->get_this_model_name();
3115
-                if (
3116
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3117
-                    && $this->get_one_from_cache($relation_name)->ID()
3118
-                ) {
3119
-                    $this->clear_cache(
3120
-                        $relation_name,
3121
-                        $this->get_one_from_cache($relation_name)->ID()
3122
-                    );
3123
-                }
3124
-            }
3125
-        }
3126
-        $this->_props_n_values_provided_in_constructor = array();
3127
-        $properties_to_serialize                       = get_object_vars($this);
3128
-        //don't serialize the model. It's big and that risks recursion
3129
-        unset($properties_to_serialize['_model']);
3130
-        return array_keys($properties_to_serialize);
3131
-    }
3132
-
3133
-
3134
-    /**
3135
-     * restore _props_n_values_provided_in_constructor
3136
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3137
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3138
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3139
-     */
3140
-    public function __wakeup()
3141
-    {
3142
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3143
-    }
3144
-
3145
-
3146
-    /**
3147
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3148
-     * distinct with the clone host instance are also cloned.
3149
-     */
3150
-    public function __clone()
3151
-    {
3152
-        //handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3153
-        foreach ($this->_fields as $field => $value) {
3154
-            if ($value instanceof DateTime) {
3155
-                $this->_fields[$field] = clone $value;
3156
-            }
3157
-        }
3158
-    }
18
+	/**
19
+	 * This is an array of the original properties and values provided during construction
20
+	 * of this model object. (keys are model field names, values are their values).
21
+	 * This list is important to remember so that when we are merging data from the db, we know
22
+	 * which values to override and which to not override.
23
+	 *
24
+	 * @var array
25
+	 */
26
+	protected $_props_n_values_provided_in_constructor;
27
+
28
+	/**
29
+	 * Timezone
30
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
31
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
32
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
33
+	 * access to it.
34
+	 *
35
+	 * @var string
36
+	 */
37
+	protected $_timezone;
38
+
39
+	/**
40
+	 * date format
41
+	 * pattern or format for displaying dates
42
+	 *
43
+	 * @var string $_dt_frmt
44
+	 */
45
+	protected $_dt_frmt;
46
+
47
+	/**
48
+	 * time format
49
+	 * pattern or format for displaying time
50
+	 *
51
+	 * @var string $_tm_frmt
52
+	 */
53
+	protected $_tm_frmt;
54
+
55
+	/**
56
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
57
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
58
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
59
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
60
+	 *
61
+	 * @var array
62
+	 */
63
+	protected $_cached_properties = array();
64
+
65
+	/**
66
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
67
+	 * single
68
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
69
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
70
+	 * all others have an array)
71
+	 *
72
+	 * @var array
73
+	 */
74
+	protected $_model_relations = array();
75
+
76
+	/**
77
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
78
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
79
+	 *
80
+	 * @var array
81
+	 */
82
+	protected $_fields = array();
83
+
84
+	/**
85
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
86
+	 * For example, we might create model objects intended to only be used for the duration
87
+	 * of this request and to be thrown away, and if they were accidentally saved
88
+	 * it would be a bug.
89
+	 */
90
+	protected $_allow_persist = true;
91
+
92
+	/**
93
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
94
+	 */
95
+	protected $_has_changes = false;
96
+
97
+	/**
98
+	 * @var EEM_Base
99
+	 */
100
+	protected $_model;
101
+
102
+	/**
103
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
104
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
105
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
106
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
107
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
108
+	 * array as:
109
+	 * array(
110
+	 *  'Registration_Count' => 24
111
+	 * );
112
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
113
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
114
+	 * info)
115
+	 *
116
+	 * @var array
117
+	 */
118
+	protected $custom_selection_results = array();
119
+
120
+
121
+	/**
122
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
123
+	 * play nice
124
+	 *
125
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
126
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
127
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
128
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
129
+	 *                                                         corresponding db model or not.
130
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
131
+	 *                                                         be in when instantiating a EE_Base_Class object.
132
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
133
+	 *                                                         value is the date_format and second value is the time
134
+	 *                                                         format.
135
+	 * @throws InvalidArgumentException
136
+	 * @throws InvalidInterfaceException
137
+	 * @throws InvalidDataTypeException
138
+	 * @throws EE_Error
139
+	 * @throws ReflectionException
140
+	 */
141
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
142
+	{
143
+		$className = get_class($this);
144
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
145
+		$model        = $this->get_model();
146
+		$model_fields = $model->field_settings(false);
147
+		// ensure $fieldValues is an array
148
+		$fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
149
+		// verify client code has not passed any invalid field names
150
+		foreach ($fieldValues as $field_name => $field_value) {
151
+			if (! isset($model_fields[ $field_name ])) {
152
+				throw new EE_Error(
153
+					sprintf(
154
+						esc_html__(
155
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
156
+							'event_espresso'
157
+						),
158
+						$field_name,
159
+						get_class($this),
160
+						implode(', ', array_keys($model_fields))
161
+					)
162
+				);
163
+			}
164
+		}
165
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
166
+		if (! empty($date_formats) && is_array($date_formats)) {
167
+			list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
168
+		} else {
169
+			//set default formats for date and time
170
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
171
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
172
+		}
173
+		//if db model is instantiating
174
+		if ($bydb) {
175
+			//client code has indicated these field values are from the database
176
+			foreach ($model_fields as $fieldName => $field) {
177
+				$this->set_from_db(
178
+					$fieldName,
179
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
180
+				);
181
+			}
182
+		} else {
183
+			//we're constructing a brand
184
+			//new instance of the model object. Generally, this means we'll need to do more field validation
185
+			foreach ($model_fields as $fieldName => $field) {
186
+				$this->set(
187
+					$fieldName,
188
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null, true
189
+				);
190
+			}
191
+		}
192
+		//remember what values were passed to this constructor
193
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
194
+		//remember in entity mapper
195
+		if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
196
+			$model->add_to_entity_map($this);
197
+		}
198
+		//setup all the relations
199
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
200
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
+				$this->_model_relations[ $relation_name ] = null;
202
+			} else {
203
+				$this->_model_relations[ $relation_name ] = array();
204
+			}
205
+		}
206
+		/**
207
+		 * Action done at the end of each model object construction
208
+		 *
209
+		 * @param EE_Base_Class $this the model object just created
210
+		 */
211
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
+	}
213
+
214
+
215
+	/**
216
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
217
+	 *
218
+	 * @return boolean
219
+	 */
220
+	public function allow_persist()
221
+	{
222
+		return $this->_allow_persist;
223
+	}
224
+
225
+
226
+	/**
227
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
228
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
229
+	 * you got new information that somehow made you change your mind.
230
+	 *
231
+	 * @param boolean $allow_persist
232
+	 * @return boolean
233
+	 */
234
+	public function set_allow_persist($allow_persist)
235
+	{
236
+		return $this->_allow_persist = $allow_persist;
237
+	}
238
+
239
+
240
+	/**
241
+	 * Gets the field's original value when this object was constructed during this request.
242
+	 * This can be helpful when determining if a model object has changed or not
243
+	 *
244
+	 * @param string $field_name
245
+	 * @return mixed|null
246
+	 * @throws ReflectionException
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidInterfaceException
249
+	 * @throws InvalidDataTypeException
250
+	 * @throws EE_Error
251
+	 */
252
+	public function get_original($field_name)
253
+	{
254
+		if (isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
256
+		) {
257
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
+		}
259
+		return null;
260
+	}
261
+
262
+
263
+	/**
264
+	 * @param EE_Base_Class $obj
265
+	 * @return string
266
+	 */
267
+	public function get_class($obj)
268
+	{
269
+		return get_class($obj);
270
+	}
271
+
272
+
273
+	/**
274
+	 * Overrides parent because parent expects old models.
275
+	 * This also doesn't do any validation, and won't work for serialized arrays
276
+	 *
277
+	 * @param    string $field_name
278
+	 * @param    mixed  $field_value
279
+	 * @param bool      $use_default
280
+	 * @throws InvalidArgumentException
281
+	 * @throws InvalidInterfaceException
282
+	 * @throws InvalidDataTypeException
283
+	 * @throws EE_Error
284
+	 * @throws ReflectionException
285
+	 * @throws ReflectionException
286
+	 * @throws ReflectionException
287
+	 */
288
+	public function set($field_name, $field_value, $use_default = false)
289
+	{
290
+		// if not using default and nothing has changed, and object has already been setup (has ID),
291
+		// then don't do anything
292
+		if (
293
+			! $use_default
294
+			&& $this->_fields[ $field_name ] === $field_value
295
+			&& $this->ID()
296
+		) {
297
+			return;
298
+		}
299
+		$model              = $this->get_model();
300
+		$this->_has_changes = true;
301
+		$field_obj          = $model->field_settings_for($field_name);
302
+		if ($field_obj instanceof EE_Model_Field_Base) {
303
+			//			if ( method_exists( $field_obj, 'set_timezone' )) {
304
+			if ($field_obj instanceof EE_Datetime_Field) {
305
+				$field_obj->set_timezone($this->_timezone);
306
+				$field_obj->set_date_format($this->_dt_frmt);
307
+				$field_obj->set_time_format($this->_tm_frmt);
308
+			}
309
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
310
+			//should the value be null?
311
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
313
+				/**
314
+				 * To save having to refactor all the models, if a default value is used for a
315
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
+				 * object.
318
+				 *
319
+				 * @since 4.6.10+
320
+				 */
321
+				if (
322
+					$field_obj instanceof EE_Datetime_Field
323
+					&& $this->_fields[ $field_name ] !== null
324
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
325
+				) {
326
+					empty($this->_fields[ $field_name ])
327
+						? $this->set($field_name, time())
328
+						: $this->set($field_name, $this->_fields[ $field_name ]);
329
+				}
330
+			} else {
331
+				$this->_fields[ $field_name ] = $holder_of_value;
332
+			}
333
+			//if we're not in the constructor...
334
+			//now check if what we set was a primary key
335
+			if (
336
+				//note: props_n_values_provided_in_constructor is only set at the END of the constructor
337
+				$this->_props_n_values_provided_in_constructor
338
+				&& $field_value
339
+				&& $field_name === $model->primary_key_name()
340
+			) {
341
+				//if so, we want all this object's fields to be filled either with
342
+				//what we've explicitly set on this model
343
+				//or what we have in the db
344
+				// echo "setting primary key!";
345
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
346
+				$obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
+				foreach ($fields_on_model as $field_obj) {
348
+					if (! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
349
+						&& $field_obj->get_name() !== $field_name
350
+					) {
351
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
352
+					}
353
+				}
354
+				//oh this model object has an ID? well make sure its in the entity mapper
355
+				$model->add_to_entity_map($this);
356
+			}
357
+			//let's unset any cache for this field_name from the $_cached_properties property.
358
+			$this->_clear_cached_property($field_name);
359
+		} else {
360
+			throw new EE_Error(
361
+				sprintf(
362
+					esc_html__(
363
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
364
+						'event_espresso'
365
+					),
366
+					$field_name
367
+				)
368
+			);
369
+		}
370
+	}
371
+
372
+
373
+	/**
374
+	 * Set custom select values for model.
375
+	 *
376
+	 * @param array $custom_select_values
377
+	 */
378
+	public function setCustomSelectsValues(array $custom_select_values)
379
+	{
380
+		$this->custom_selection_results = $custom_select_values;
381
+	}
382
+
383
+
384
+	/**
385
+	 * Returns the custom select value for the provided alias if its set.
386
+	 * If not set, returns null.
387
+	 *
388
+	 * @param string $alias
389
+	 * @return string|int|float|null
390
+	 */
391
+	public function getCustomSelect($alias)
392
+	{
393
+		return isset($this->custom_selection_results[ $alias ])
394
+			? $this->custom_selection_results[ $alias ]
395
+			: null;
396
+	}
397
+
398
+
399
+	/**
400
+	 * This sets the field value on the db column if it exists for the given $column_name or
401
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
402
+	 *
403
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
404
+	 * @param string $field_name  Must be the exact column name.
405
+	 * @param mixed  $field_value The value to set.
406
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
407
+	 * @throws InvalidArgumentException
408
+	 * @throws InvalidInterfaceException
409
+	 * @throws InvalidDataTypeException
410
+	 * @throws EE_Error
411
+	 * @throws ReflectionException
412
+	 */
413
+	public function set_field_or_extra_meta($field_name, $field_value)
414
+	{
415
+		if ($this->get_model()->has_field($field_name)) {
416
+			$this->set($field_name, $field_value);
417
+			return true;
418
+		}
419
+		//ensure this object is saved first so that extra meta can be properly related.
420
+		$this->save();
421
+		return $this->update_extra_meta($field_name, $field_value);
422
+	}
423
+
424
+
425
+	/**
426
+	 * This retrieves the value of the db column set on this class or if that's not present
427
+	 * it will attempt to retrieve from extra_meta if found.
428
+	 * Example Usage:
429
+	 * Via EE_Message child class:
430
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
431
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
432
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
433
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
434
+	 * value for those extra fields dynamically via the EE_message object.
435
+	 *
436
+	 * @param  string $field_name expecting the fully qualified field name.
437
+	 * @return mixed|null  value for the field if found.  null if not found.
438
+	 * @throws ReflectionException
439
+	 * @throws InvalidArgumentException
440
+	 * @throws InvalidInterfaceException
441
+	 * @throws InvalidDataTypeException
442
+	 * @throws EE_Error
443
+	 */
444
+	public function get_field_or_extra_meta($field_name)
445
+	{
446
+		if ($this->get_model()->has_field($field_name)) {
447
+			$column_value = $this->get($field_name);
448
+		} else {
449
+			//This isn't a column in the main table, let's see if it is in the extra meta.
450
+			$column_value = $this->get_extra_meta($field_name, true, null);
451
+		}
452
+		return $column_value;
453
+	}
454
+
455
+
456
+	/**
457
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
458
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
459
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
460
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
461
+	 *
462
+	 * @access public
463
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
464
+	 * @return void
465
+	 * @throws InvalidArgumentException
466
+	 * @throws InvalidInterfaceException
467
+	 * @throws InvalidDataTypeException
468
+	 * @throws EE_Error
469
+	 * @throws ReflectionException
470
+	 */
471
+	public function set_timezone($timezone = '')
472
+	{
473
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
474
+		//make sure we clear all cached properties because they won't be relevant now
475
+		$this->_clear_cached_properties();
476
+		//make sure we update field settings and the date for all EE_Datetime_Fields
477
+		$model_fields = $this->get_model()->field_settings(false);
478
+		foreach ($model_fields as $field_name => $field_obj) {
479
+			if ($field_obj instanceof EE_Datetime_Field) {
480
+				$field_obj->set_timezone($this->_timezone);
481
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
482
+					EEH_DTT_Helper::setTimezone($this->_fields[$field_name], new DateTimeZone($this->_timezone));
483
+				}
484
+			}
485
+		}
486
+	}
487
+
488
+
489
+	/**
490
+	 * This just returns whatever is set for the current timezone.
491
+	 *
492
+	 * @access public
493
+	 * @return string timezone string
494
+	 */
495
+	public function get_timezone()
496
+	{
497
+		return $this->_timezone;
498
+	}
499
+
500
+
501
+	/**
502
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
503
+	 * internally instead of wp set date format options
504
+	 *
505
+	 * @since 4.6
506
+	 * @param string $format should be a format recognizable by PHP date() functions.
507
+	 */
508
+	public function set_date_format($format)
509
+	{
510
+		$this->_dt_frmt = $format;
511
+		//clear cached_properties because they won't be relevant now.
512
+		$this->_clear_cached_properties();
513
+	}
514
+
515
+
516
+	/**
517
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
518
+	 * class internally instead of wp set time format options.
519
+	 *
520
+	 * @since 4.6
521
+	 * @param string $format should be a format recognizable by PHP date() functions.
522
+	 */
523
+	public function set_time_format($format)
524
+	{
525
+		$this->_tm_frmt = $format;
526
+		//clear cached_properties because they won't be relevant now.
527
+		$this->_clear_cached_properties();
528
+	}
529
+
530
+
531
+	/**
532
+	 * This returns the current internal set format for the date and time formats.
533
+	 *
534
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
535
+	 *                             where the first value is the date format and the second value is the time format.
536
+	 * @return mixed string|array
537
+	 */
538
+	public function get_format($full = true)
539
+	{
540
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
541
+	}
542
+
543
+
544
+	/**
545
+	 * cache
546
+	 * stores the passed model object on the current model object.
547
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
548
+	 *
549
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
550
+	 *                                       'Registration' associated with this model object
551
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
552
+	 *                                       that could be a payment or a registration)
553
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
554
+	 *                                       items which will be stored in an array on this object
555
+	 * @throws ReflectionException
556
+	 * @throws InvalidArgumentException
557
+	 * @throws InvalidInterfaceException
558
+	 * @throws InvalidDataTypeException
559
+	 * @throws EE_Error
560
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
561
+	 *                                       related thing, no array)
562
+	 */
563
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
564
+	{
565
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
566
+		if (! $object_to_cache instanceof EE_Base_Class) {
567
+			return false;
568
+		}
569
+		// also get "how" the object is related, or throw an error
570
+		if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
571
+			throw new EE_Error(
572
+				sprintf(
573
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
574
+					$relationName,
575
+					get_class($this)
576
+				)
577
+			);
578
+		}
579
+		// how many things are related ?
580
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
581
+			// if it's a "belongs to" relationship, then there's only one related model object
582
+			// eg, if this is a registration, there's only 1 attendee for it
583
+			// so for these model objects just set it to be cached
584
+			$this->_model_relations[ $relationName ] = $object_to_cache;
585
+			$return                                  = true;
586
+		} else {
587
+			// otherwise, this is the "many" side of a one to many relationship,
588
+			// so we'll add the object to the array of related objects for that type.
589
+			// eg: if this is an event, there are many registrations for that event,
590
+			// so we cache the registrations in an array
591
+			if (! is_array($this->_model_relations[ $relationName ])) {
592
+				// if for some reason, the cached item is a model object,
593
+				// then stick that in the array, otherwise start with an empty array
594
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
595
+														   instanceof
596
+														   EE_Base_Class
597
+					? array($this->_model_relations[ $relationName ]) : array();
598
+			}
599
+			// first check for a cache_id which is normally empty
600
+			if (! empty($cache_id)) {
601
+				// if the cache_id exists, then it means we are purposely trying to cache this
602
+				// with a known key that can then be used to retrieve the object later on
603
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
604
+				$return                                               = $cache_id;
605
+			} elseif ($object_to_cache->ID()) {
606
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
607
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
608
+				$return                                                            = $object_to_cache->ID();
609
+			} else {
610
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
611
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
612
+				// move the internal pointer to the end of the array
613
+				end($this->_model_relations[ $relationName ]);
614
+				// and grab the key so that we can return it
615
+				$return = key($this->_model_relations[ $relationName ]);
616
+			}
617
+		}
618
+		return $return;
619
+	}
620
+
621
+
622
+	/**
623
+	 * For adding an item to the cached_properties property.
624
+	 *
625
+	 * @access protected
626
+	 * @param string      $fieldname the property item the corresponding value is for.
627
+	 * @param mixed       $value     The value we are caching.
628
+	 * @param string|null $cache_type
629
+	 * @return void
630
+	 * @throws ReflectionException
631
+	 * @throws InvalidArgumentException
632
+	 * @throws InvalidInterfaceException
633
+	 * @throws InvalidDataTypeException
634
+	 * @throws EE_Error
635
+	 */
636
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
637
+	{
638
+		//first make sure this property exists
639
+		$this->get_model()->field_settings_for($fieldname);
640
+		$cache_type                                            = empty($cache_type) ? 'standard' : $cache_type;
641
+		$this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
642
+	}
643
+
644
+
645
+	/**
646
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
647
+	 * This also SETS the cache if we return the actual property!
648
+	 *
649
+	 * @param string $fieldname        the name of the property we're trying to retrieve
650
+	 * @param bool   $pretty
651
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
652
+	 *                                 (in cases where the same property may be used for different outputs
653
+	 *                                 - i.e. datetime, money etc.)
654
+	 *                                 It can also accept certain pre-defined "schema" strings
655
+	 *                                 to define how to output the property.
656
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
657
+	 * @return mixed                   whatever the value for the property is we're retrieving
658
+	 * @throws ReflectionException
659
+	 * @throws InvalidArgumentException
660
+	 * @throws InvalidInterfaceException
661
+	 * @throws InvalidDataTypeException
662
+	 * @throws EE_Error
663
+	 */
664
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
665
+	{
666
+		//verify the field exists
667
+		$model = $this->get_model();
668
+		$model->field_settings_for($fieldname);
669
+		$cache_type = $pretty ? 'pretty' : 'standard';
670
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
671
+		if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
672
+			return $this->_cached_properties[ $fieldname ][ $cache_type ];
673
+		}
674
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
675
+		$this->_set_cached_property($fieldname, $value, $cache_type);
676
+		return $value;
677
+	}
678
+
679
+
680
+	/**
681
+	 * If the cache didn't fetch the needed item, this fetches it.
682
+	 *
683
+	 * @param string $fieldname
684
+	 * @param bool   $pretty
685
+	 * @param string $extra_cache_ref
686
+	 * @return mixed
687
+	 * @throws InvalidArgumentException
688
+	 * @throws InvalidInterfaceException
689
+	 * @throws InvalidDataTypeException
690
+	 * @throws EE_Error
691
+	 * @throws ReflectionException
692
+	 */
693
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
694
+	{
695
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
696
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
697
+		if ($field_obj instanceof EE_Datetime_Field) {
698
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
699
+		}
700
+		if (! isset($this->_fields[ $fieldname ])) {
701
+			$this->_fields[ $fieldname ] = null;
702
+		}
703
+		$value = $pretty
704
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
705
+			: $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
706
+		return $value;
707
+	}
708
+
709
+
710
+	/**
711
+	 * set timezone, formats, and output for EE_Datetime_Field objects
712
+	 *
713
+	 * @param \EE_Datetime_Field $datetime_field
714
+	 * @param bool               $pretty
715
+	 * @param null               $date_or_time
716
+	 * @return void
717
+	 * @throws InvalidArgumentException
718
+	 * @throws InvalidInterfaceException
719
+	 * @throws InvalidDataTypeException
720
+	 * @throws EE_Error
721
+	 */
722
+	protected function _prepare_datetime_field(
723
+		EE_Datetime_Field $datetime_field,
724
+		$pretty = false,
725
+		$date_or_time = null
726
+	) {
727
+		$datetime_field->set_timezone($this->_timezone);
728
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
729
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
730
+		//set the output returned
731
+		switch ($date_or_time) {
732
+			case 'D' :
733
+				$datetime_field->set_date_time_output('date');
734
+				break;
735
+			case 'T' :
736
+				$datetime_field->set_date_time_output('time');
737
+				break;
738
+			default :
739
+				$datetime_field->set_date_time_output();
740
+		}
741
+	}
742
+
743
+
744
+	/**
745
+	 * This just takes care of clearing out the cached_properties
746
+	 *
747
+	 * @return void
748
+	 */
749
+	protected function _clear_cached_properties()
750
+	{
751
+		$this->_cached_properties = array();
752
+	}
753
+
754
+
755
+	/**
756
+	 * This just clears out ONE property if it exists in the cache
757
+	 *
758
+	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
759
+	 * @return void
760
+	 */
761
+	protected function _clear_cached_property($property_name)
762
+	{
763
+		if (isset($this->_cached_properties[ $property_name ])) {
764
+			unset($this->_cached_properties[ $property_name ]);
765
+		}
766
+	}
767
+
768
+
769
+	/**
770
+	 * Ensures that this related thing is a model object.
771
+	 *
772
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
773
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
774
+	 * @return EE_Base_Class
775
+	 * @throws ReflectionException
776
+	 * @throws InvalidArgumentException
777
+	 * @throws InvalidInterfaceException
778
+	 * @throws InvalidDataTypeException
779
+	 * @throws EE_Error
780
+	 */
781
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
782
+	{
783
+		$other_model_instance = self::_get_model_instance_with_name(
784
+			self::_get_model_classname($model_name),
785
+			$this->_timezone
786
+		);
787
+		return $other_model_instance->ensure_is_obj($object_or_id);
788
+	}
789
+
790
+
791
+	/**
792
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
793
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
794
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
795
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
796
+	 *
797
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
798
+	 *                                                     Eg 'Registration'
799
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
800
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
801
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
802
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
803
+	 *                                                     this is HasMany or HABTM.
804
+	 * @throws ReflectionException
805
+	 * @throws InvalidArgumentException
806
+	 * @throws InvalidInterfaceException
807
+	 * @throws InvalidDataTypeException
808
+	 * @throws EE_Error
809
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
810
+	 *                                                     relation from all
811
+	 */
812
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
813
+	{
814
+		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
815
+		$index_in_cache        = '';
816
+		if (! $relationship_to_model) {
817
+			throw new EE_Error(
818
+				sprintf(
819
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
820
+					$relationName,
821
+					get_class($this)
822
+				)
823
+			);
824
+		}
825
+		if ($clear_all) {
826
+			$obj_removed                             = true;
827
+			$this->_model_relations[ $relationName ] = null;
828
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
829
+			$obj_removed                             = $this->_model_relations[ $relationName ];
830
+			$this->_model_relations[ $relationName ] = null;
831
+		} else {
832
+			if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
833
+				&& $object_to_remove_or_index_into_array->ID()
834
+			) {
835
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
836
+				if (is_array($this->_model_relations[ $relationName ])
837
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
838
+				) {
839
+					$index_found_at = null;
840
+					//find this object in the array even though it has a different key
841
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
842
+						/** @noinspection TypeUnsafeComparisonInspection */
843
+						if (
844
+							$obj instanceof EE_Base_Class
845
+							&& (
846
+								$obj == $object_to_remove_or_index_into_array
847
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
848
+							)
849
+						) {
850
+							$index_found_at = $index;
851
+							break;
852
+						}
853
+					}
854
+					if ($index_found_at) {
855
+						$index_in_cache = $index_found_at;
856
+					} else {
857
+						//it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
858
+						//if it wasn't in it to begin with. So we're done
859
+						return $object_to_remove_or_index_into_array;
860
+					}
861
+				}
862
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
863
+				//so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
864
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
865
+					/** @noinspection TypeUnsafeComparisonInspection */
866
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
867
+						$index_in_cache = $index;
868
+					}
869
+				}
870
+			} else {
871
+				$index_in_cache = $object_to_remove_or_index_into_array;
872
+			}
873
+			//supposedly we've found it. But it could just be that the client code
874
+			//provided a bad index/object
875
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
876
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
877
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
878
+			} else {
879
+				//that thing was never cached anyways.
880
+				$obj_removed = null;
881
+			}
882
+		}
883
+		return $obj_removed;
884
+	}
885
+
886
+
887
+	/**
888
+	 * update_cache_after_object_save
889
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
890
+	 * obtained after being saved to the db
891
+	 *
892
+	 * @param string        $relationName       - the type of object that is cached
893
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
894
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
895
+	 * @return boolean TRUE on success, FALSE on fail
896
+	 * @throws ReflectionException
897
+	 * @throws InvalidArgumentException
898
+	 * @throws InvalidInterfaceException
899
+	 * @throws InvalidDataTypeException
900
+	 * @throws EE_Error
901
+	 */
902
+	public function update_cache_after_object_save(
903
+		$relationName,
904
+		EE_Base_Class $newly_saved_object,
905
+		$current_cache_id = ''
906
+	) {
907
+		// verify that incoming object is of the correct type
908
+		$obj_class = 'EE_' . $relationName;
909
+		if ($newly_saved_object instanceof $obj_class) {
910
+			/* @type EE_Base_Class $newly_saved_object */
911
+			// now get the type of relation
912
+			$relationship_to_model = $this->get_model()->related_settings_for($relationName);
913
+			// if this is a 1:1 relationship
914
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
915
+				// then just replace the cached object with the newly saved object
916
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
917
+				return true;
918
+				// or if it's some kind of sordid feral polyamorous relationship...
919
+			}
920
+			if (is_array($this->_model_relations[ $relationName ])
921
+					  && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
922
+			) {
923
+				// then remove the current cached item
924
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
925
+				// and cache the newly saved object using it's new ID
926
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
927
+				return true;
928
+			}
929
+		}
930
+		return false;
931
+	}
932
+
933
+
934
+	/**
935
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
936
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
937
+	 *
938
+	 * @param string $relationName
939
+	 * @return EE_Base_Class
940
+	 */
941
+	public function get_one_from_cache($relationName)
942
+	{
943
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
944
+			? $this->_model_relations[ $relationName ]
945
+			: null;
946
+		if (is_array($cached_array_or_object)) {
947
+			return array_shift($cached_array_or_object);
948
+		}
949
+		return $cached_array_or_object;
950
+	}
951
+
952
+
953
+	/**
954
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
955
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
956
+	 *
957
+	 * @param string $relationName
958
+	 * @throws ReflectionException
959
+	 * @throws InvalidArgumentException
960
+	 * @throws InvalidInterfaceException
961
+	 * @throws InvalidDataTypeException
962
+	 * @throws EE_Error
963
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
964
+	 */
965
+	public function get_all_from_cache($relationName)
966
+	{
967
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
968
+		// if the result is not an array, but exists, make it an array
969
+		$objects = is_array($objects) ? $objects : array($objects);
970
+		//bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
971
+		//basically, if this model object was stored in the session, and these cached model objects
972
+		//already have IDs, let's make sure they're in their model's entity mapper
973
+		//otherwise we will have duplicates next time we call
974
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
975
+		$model = EE_Registry::instance()->load_model($relationName);
976
+		foreach ($objects as $model_object) {
977
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
978
+				//ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
979
+				if ($model_object->ID()) {
980
+					$model->add_to_entity_map($model_object);
981
+				}
982
+			} else {
983
+				throw new EE_Error(
984
+					sprintf(
985
+						esc_html__(
986
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
987
+							'event_espresso'
988
+						),
989
+						$relationName,
990
+						gettype($model_object)
991
+					)
992
+				);
993
+			}
994
+		}
995
+		return $objects;
996
+	}
997
+
998
+
999
+	/**
1000
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1001
+	 * matching the given query conditions.
1002
+	 *
1003
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1004
+	 * @param int   $limit              How many objects to return.
1005
+	 * @param array $query_params       Any additional conditions on the query.
1006
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1007
+	 *                                  you can indicate just the columns you want returned
1008
+	 * @return array|EE_Base_Class[]
1009
+	 * @throws ReflectionException
1010
+	 * @throws InvalidArgumentException
1011
+	 * @throws InvalidInterfaceException
1012
+	 * @throws InvalidDataTypeException
1013
+	 * @throws EE_Error
1014
+	 */
1015
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1016
+	{
1017
+		$model         = $this->get_model();
1018
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1019
+			? $model->get_primary_key_field()->get_name()
1020
+			: $field_to_order_by;
1021
+		$current_value = ! empty($field) ? $this->get($field) : null;
1022
+		if (empty($field) || empty($current_value)) {
1023
+			return array();
1024
+		}
1025
+		return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1026
+	}
1027
+
1028
+
1029
+	/**
1030
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1031
+	 * matching the given query conditions.
1032
+	 *
1033
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1034
+	 * @param int   $limit              How many objects to return.
1035
+	 * @param array $query_params       Any additional conditions on the query.
1036
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1037
+	 *                                  you can indicate just the columns you want returned
1038
+	 * @return array|EE_Base_Class[]
1039
+	 * @throws ReflectionException
1040
+	 * @throws InvalidArgumentException
1041
+	 * @throws InvalidInterfaceException
1042
+	 * @throws InvalidDataTypeException
1043
+	 * @throws EE_Error
1044
+	 */
1045
+	public function previous_x(
1046
+		$field_to_order_by = null,
1047
+		$limit = 1,
1048
+		$query_params = array(),
1049
+		$columns_to_select = null
1050
+	) {
1051
+		$model         = $this->get_model();
1052
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1053
+			? $model->get_primary_key_field()->get_name()
1054
+			: $field_to_order_by;
1055
+		$current_value = ! empty($field) ? $this->get($field) : null;
1056
+		if (empty($field) || empty($current_value)) {
1057
+			return array();
1058
+		}
1059
+		return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1060
+	}
1061
+
1062
+
1063
+	/**
1064
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1065
+	 * matching the given query conditions.
1066
+	 *
1067
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1068
+	 * @param array $query_params       Any additional conditions on the query.
1069
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1070
+	 *                                  you can indicate just the columns you want returned
1071
+	 * @return array|EE_Base_Class
1072
+	 * @throws ReflectionException
1073
+	 * @throws InvalidArgumentException
1074
+	 * @throws InvalidInterfaceException
1075
+	 * @throws InvalidDataTypeException
1076
+	 * @throws EE_Error
1077
+	 */
1078
+	public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1079
+	{
1080
+		$model         = $this->get_model();
1081
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1082
+			? $model->get_primary_key_field()->get_name()
1083
+			: $field_to_order_by;
1084
+		$current_value = ! empty($field) ? $this->get($field) : null;
1085
+		if (empty($field) || empty($current_value)) {
1086
+			return array();
1087
+		}
1088
+		return $model->next($current_value, $field, $query_params, $columns_to_select);
1089
+	}
1090
+
1091
+
1092
+	/**
1093
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1094
+	 * matching the given query conditions.
1095
+	 *
1096
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1097
+	 * @param array $query_params       Any additional conditions on the query.
1098
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1099
+	 *                                  you can indicate just the column you want returned
1100
+	 * @return array|EE_Base_Class
1101
+	 * @throws ReflectionException
1102
+	 * @throws InvalidArgumentException
1103
+	 * @throws InvalidInterfaceException
1104
+	 * @throws InvalidDataTypeException
1105
+	 * @throws EE_Error
1106
+	 */
1107
+	public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1108
+	{
1109
+		$model         = $this->get_model();
1110
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1111
+			? $model->get_primary_key_field()->get_name()
1112
+			: $field_to_order_by;
1113
+		$current_value = ! empty($field) ? $this->get($field) : null;
1114
+		if (empty($field) || empty($current_value)) {
1115
+			return array();
1116
+		}
1117
+		return $model->previous($current_value, $field, $query_params, $columns_to_select);
1118
+	}
1119
+
1120
+
1121
+	/**
1122
+	 * Overrides parent because parent expects old models.
1123
+	 * This also doesn't do any validation, and won't work for serialized arrays
1124
+	 *
1125
+	 * @param string $field_name
1126
+	 * @param mixed  $field_value_from_db
1127
+	 * @throws ReflectionException
1128
+	 * @throws InvalidArgumentException
1129
+	 * @throws InvalidInterfaceException
1130
+	 * @throws InvalidDataTypeException
1131
+	 * @throws EE_Error
1132
+	 */
1133
+	public function set_from_db($field_name, $field_value_from_db)
1134
+	{
1135
+		$field_obj = $this->get_model()->field_settings_for($field_name);
1136
+		if ($field_obj instanceof EE_Model_Field_Base) {
1137
+			//you would think the DB has no NULLs for non-null label fields right? wrong!
1138
+			//eg, a CPT model object could have an entry in the posts table, but no
1139
+			//entry in the meta table. Meaning that all its columns in the meta table
1140
+			//are null! yikes! so when we find one like that, use defaults for its meta columns
1141
+			if ($field_value_from_db === null) {
1142
+				if ($field_obj->is_nullable()) {
1143
+					//if the field allows nulls, then let it be null
1144
+					$field_value = null;
1145
+				} else {
1146
+					$field_value = $field_obj->get_default_value();
1147
+				}
1148
+			} else {
1149
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1150
+			}
1151
+			$this->_fields[ $field_name ] = $field_value;
1152
+			$this->_clear_cached_property($field_name);
1153
+		}
1154
+	}
1155
+
1156
+
1157
+	/**
1158
+	 * verifies that the specified field is of the correct type
1159
+	 *
1160
+	 * @param string $field_name
1161
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1162
+	 *                                (in cases where the same property may be used for different outputs
1163
+	 *                                - i.e. datetime, money etc.)
1164
+	 * @return mixed
1165
+	 * @throws ReflectionException
1166
+	 * @throws InvalidArgumentException
1167
+	 * @throws InvalidInterfaceException
1168
+	 * @throws InvalidDataTypeException
1169
+	 * @throws EE_Error
1170
+	 */
1171
+	public function get($field_name, $extra_cache_ref = null)
1172
+	{
1173
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1174
+	}
1175
+
1176
+
1177
+	/**
1178
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1179
+	 *
1180
+	 * @param  string $field_name A valid fieldname
1181
+	 * @return mixed              Whatever the raw value stored on the property is.
1182
+	 * @throws ReflectionException
1183
+	 * @throws InvalidArgumentException
1184
+	 * @throws InvalidInterfaceException
1185
+	 * @throws InvalidDataTypeException
1186
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1187
+	 */
1188
+	public function get_raw($field_name)
1189
+	{
1190
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1191
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1192
+			? $this->_fields[ $field_name ]->format('U')
1193
+			: $this->_fields[ $field_name ];
1194
+	}
1195
+
1196
+
1197
+	/**
1198
+	 * This is used to return the internal DateTime object used for a field that is a
1199
+	 * EE_Datetime_Field.
1200
+	 *
1201
+	 * @param string $field_name               The field name retrieving the DateTime object.
1202
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1203
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1204
+	 *                                         EE_Datetime_Field and but the field value is null, then
1205
+	 *                                         just null is returned (because that indicates that likely
1206
+	 *                                         this field is nullable).
1207
+	 * @throws InvalidArgumentException
1208
+	 * @throws InvalidDataTypeException
1209
+	 * @throws InvalidInterfaceException
1210
+	 * @throws ReflectionException
1211
+	 */
1212
+	public function get_DateTime_object($field_name)
1213
+	{
1214
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1215
+		if (! $field_settings instanceof EE_Datetime_Field) {
1216
+			EE_Error::add_error(
1217
+				sprintf(
1218
+					esc_html__(
1219
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1220
+						'event_espresso'
1221
+					),
1222
+					$field_name
1223
+				),
1224
+				__FILE__,
1225
+				__FUNCTION__,
1226
+				__LINE__
1227
+			);
1228
+			return false;
1229
+		}
1230
+		return isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime
1231
+			? clone $this->_fields[$field_name]
1232
+			: null;
1233
+	}
1234
+
1235
+
1236
+	/**
1237
+	 * To be used in template to immediately echo out the value, and format it for output.
1238
+	 * Eg, should call stripslashes and whatnot before echoing
1239
+	 *
1240
+	 * @param string $field_name      the name of the field as it appears in the DB
1241
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1242
+	 *                                (in cases where the same property may be used for different outputs
1243
+	 *                                - i.e. datetime, money etc.)
1244
+	 * @return void
1245
+	 * @throws ReflectionException
1246
+	 * @throws InvalidArgumentException
1247
+	 * @throws InvalidInterfaceException
1248
+	 * @throws InvalidDataTypeException
1249
+	 * @throws EE_Error
1250
+	 */
1251
+	public function e($field_name, $extra_cache_ref = null)
1252
+	{
1253
+		echo $this->get_pretty($field_name, $extra_cache_ref);
1254
+	}
1255
+
1256
+
1257
+	/**
1258
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1259
+	 * can be easily used as the value of form input.
1260
+	 *
1261
+	 * @param string $field_name
1262
+	 * @return void
1263
+	 * @throws ReflectionException
1264
+	 * @throws InvalidArgumentException
1265
+	 * @throws InvalidInterfaceException
1266
+	 * @throws InvalidDataTypeException
1267
+	 * @throws EE_Error
1268
+	 */
1269
+	public function f($field_name)
1270
+	{
1271
+		$this->e($field_name, 'form_input');
1272
+	}
1273
+
1274
+
1275
+	/**
1276
+	 * Same as `f()` but just returns the value instead of echoing it
1277
+	 *
1278
+	 * @param string $field_name
1279
+	 * @return string
1280
+	 * @throws ReflectionException
1281
+	 * @throws InvalidArgumentException
1282
+	 * @throws InvalidInterfaceException
1283
+	 * @throws InvalidDataTypeException
1284
+	 * @throws EE_Error
1285
+	 */
1286
+	public function get_f($field_name)
1287
+	{
1288
+		return (string) $this->get_pretty($field_name, 'form_input');
1289
+	}
1290
+
1291
+
1292
+	/**
1293
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1294
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1295
+	 * to see what options are available.
1296
+	 *
1297
+	 * @param string $field_name
1298
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1299
+	 *                                (in cases where the same property may be used for different outputs
1300
+	 *                                - i.e. datetime, money etc.)
1301
+	 * @return mixed
1302
+	 * @throws ReflectionException
1303
+	 * @throws InvalidArgumentException
1304
+	 * @throws InvalidInterfaceException
1305
+	 * @throws InvalidDataTypeException
1306
+	 * @throws EE_Error
1307
+	 */
1308
+	public function get_pretty($field_name, $extra_cache_ref = null)
1309
+	{
1310
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1311
+	}
1312
+
1313
+
1314
+	/**
1315
+	 * This simply returns the datetime for the given field name
1316
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1317
+	 * (and the equivalent e_date, e_time, e_datetime).
1318
+	 *
1319
+	 * @access   protected
1320
+	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1321
+	 * @param string   $dt_frmt      valid datetime format used for date
1322
+	 *                               (if '' then we just use the default on the field,
1323
+	 *                               if NULL we use the last-used format)
1324
+	 * @param string   $tm_frmt      Same as above except this is for time format
1325
+	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1326
+	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1327
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1328
+	 *                               if field is not a valid dtt field, or void if echoing
1329
+	 * @throws ReflectionException
1330
+	 * @throws InvalidArgumentException
1331
+	 * @throws InvalidInterfaceException
1332
+	 * @throws InvalidDataTypeException
1333
+	 * @throws EE_Error
1334
+	 */
1335
+	protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1336
+	{
1337
+		// clear cached property
1338
+		$this->_clear_cached_property($field_name);
1339
+		//reset format properties because they are used in get()
1340
+		$this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1341
+		$this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1342
+		if ($echo) {
1343
+			$this->e($field_name, $date_or_time);
1344
+			return '';
1345
+		}
1346
+		return $this->get($field_name, $date_or_time);
1347
+	}
1348
+
1349
+
1350
+	/**
1351
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1352
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1353
+	 * other echoes the pretty value for dtt)
1354
+	 *
1355
+	 * @param  string $field_name name of model object datetime field holding the value
1356
+	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1357
+	 * @return string            datetime value formatted
1358
+	 * @throws ReflectionException
1359
+	 * @throws InvalidArgumentException
1360
+	 * @throws InvalidInterfaceException
1361
+	 * @throws InvalidDataTypeException
1362
+	 * @throws EE_Error
1363
+	 */
1364
+	public function get_date($field_name, $format = '')
1365
+	{
1366
+		return $this->_get_datetime($field_name, $format, null, 'D');
1367
+	}
1368
+
1369
+
1370
+	/**
1371
+	 * @param        $field_name
1372
+	 * @param string $format
1373
+	 * @throws ReflectionException
1374
+	 * @throws InvalidArgumentException
1375
+	 * @throws InvalidInterfaceException
1376
+	 * @throws InvalidDataTypeException
1377
+	 * @throws EE_Error
1378
+	 */
1379
+	public function e_date($field_name, $format = '')
1380
+	{
1381
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1382
+	}
1383
+
1384
+
1385
+	/**
1386
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1387
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1388
+	 * other echoes the pretty value for dtt)
1389
+	 *
1390
+	 * @param  string $field_name name of model object datetime field holding the value
1391
+	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1392
+	 * @return string             datetime value formatted
1393
+	 * @throws ReflectionException
1394
+	 * @throws InvalidArgumentException
1395
+	 * @throws InvalidInterfaceException
1396
+	 * @throws InvalidDataTypeException
1397
+	 * @throws EE_Error
1398
+	 */
1399
+	public function get_time($field_name, $format = '')
1400
+	{
1401
+		return $this->_get_datetime($field_name, null, $format, 'T');
1402
+	}
1403
+
1404
+
1405
+	/**
1406
+	 * @param        $field_name
1407
+	 * @param string $format
1408
+	 * @throws ReflectionException
1409
+	 * @throws InvalidArgumentException
1410
+	 * @throws InvalidInterfaceException
1411
+	 * @throws InvalidDataTypeException
1412
+	 * @throws EE_Error
1413
+	 */
1414
+	public function e_time($field_name, $format = '')
1415
+	{
1416
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1417
+	}
1418
+
1419
+
1420
+	/**
1421
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1422
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1423
+	 * other echoes the pretty value for dtt)
1424
+	 *
1425
+	 * @param  string $field_name name of model object datetime field holding the value
1426
+	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1427
+	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1428
+	 * @return string             datetime value formatted
1429
+	 * @throws ReflectionException
1430
+	 * @throws InvalidArgumentException
1431
+	 * @throws InvalidInterfaceException
1432
+	 * @throws InvalidDataTypeException
1433
+	 * @throws EE_Error
1434
+	 */
1435
+	public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1436
+	{
1437
+		return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1438
+	}
1439
+
1440
+
1441
+	/**
1442
+	 * @param string $field_name
1443
+	 * @param string $dt_frmt
1444
+	 * @param string $tm_frmt
1445
+	 * @throws ReflectionException
1446
+	 * @throws InvalidArgumentException
1447
+	 * @throws InvalidInterfaceException
1448
+	 * @throws InvalidDataTypeException
1449
+	 * @throws EE_Error
1450
+	 */
1451
+	public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1452
+	{
1453
+		$this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1454
+	}
1455
+
1456
+
1457
+	/**
1458
+	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1459
+	 *
1460
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1461
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1462
+	 *                           on the object will be used.
1463
+	 * @return string Date and time string in set locale or false if no field exists for the given
1464
+	 * @throws ReflectionException
1465
+	 * @throws InvalidArgumentException
1466
+	 * @throws InvalidInterfaceException
1467
+	 * @throws InvalidDataTypeException
1468
+	 * @throws EE_Error
1469
+	 *                           field name.
1470
+	 */
1471
+	public function get_i18n_datetime($field_name, $format = '')
1472
+	{
1473
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1474
+		return date_i18n(
1475
+			$format,
1476
+			EEH_DTT_Helper::get_timestamp_with_offset(
1477
+				$this->get_raw($field_name),
1478
+				$this->_timezone
1479
+			)
1480
+		);
1481
+	}
1482
+
1483
+
1484
+	/**
1485
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1486
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1487
+	 * thrown.
1488
+	 *
1489
+	 * @param  string $field_name The field name being checked
1490
+	 * @throws ReflectionException
1491
+	 * @throws InvalidArgumentException
1492
+	 * @throws InvalidInterfaceException
1493
+	 * @throws InvalidDataTypeException
1494
+	 * @throws EE_Error
1495
+	 * @return EE_Datetime_Field
1496
+	 */
1497
+	protected function _get_dtt_field_settings($field_name)
1498
+	{
1499
+		$field = $this->get_model()->field_settings_for($field_name);
1500
+		//check if field is dtt
1501
+		if ($field instanceof EE_Datetime_Field) {
1502
+			return $field;
1503
+		}
1504
+		throw new EE_Error(
1505
+			sprintf(
1506
+				esc_html__(
1507
+					'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',
1508
+					'event_espresso'
1509
+				),
1510
+				$field_name,
1511
+				self::_get_model_classname(get_class($this))
1512
+			)
1513
+		);
1514
+	}
1515
+
1516
+
1517
+
1518
+
1519
+	/**
1520
+	 * NOTE ABOUT BELOW:
1521
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1522
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1523
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1524
+	 * method and make sure you send the entire datetime value for setting.
1525
+	 */
1526
+	/**
1527
+	 * sets the time on a datetime property
1528
+	 *
1529
+	 * @access protected
1530
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1531
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1532
+	 * @throws ReflectionException
1533
+	 * @throws InvalidArgumentException
1534
+	 * @throws InvalidInterfaceException
1535
+	 * @throws InvalidDataTypeException
1536
+	 * @throws EE_Error
1537
+	 */
1538
+	protected function _set_time_for($time, $fieldname)
1539
+	{
1540
+		$this->_set_date_time('T', $time, $fieldname);
1541
+	}
1542
+
1543
+
1544
+	/**
1545
+	 * sets the date on a datetime property
1546
+	 *
1547
+	 * @access protected
1548
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1549
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1550
+	 * @throws ReflectionException
1551
+	 * @throws InvalidArgumentException
1552
+	 * @throws InvalidInterfaceException
1553
+	 * @throws InvalidDataTypeException
1554
+	 * @throws EE_Error
1555
+	 */
1556
+	protected function _set_date_for($date, $fieldname)
1557
+	{
1558
+		$this->_set_date_time('D', $date, $fieldname);
1559
+	}
1560
+
1561
+
1562
+	/**
1563
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1564
+	 * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1565
+	 *
1566
+	 * @access protected
1567
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1568
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1569
+	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1570
+	 *                                        EE_Datetime_Field property)
1571
+	 * @throws ReflectionException
1572
+	 * @throws InvalidArgumentException
1573
+	 * @throws InvalidInterfaceException
1574
+	 * @throws InvalidDataTypeException
1575
+	 * @throws EE_Error
1576
+	 */
1577
+	protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1578
+	{
1579
+		$field = $this->_get_dtt_field_settings($fieldname);
1580
+		$field->set_timezone($this->_timezone);
1581
+		$field->set_date_format($this->_dt_frmt);
1582
+		$field->set_time_format($this->_tm_frmt);
1583
+		switch ($what) {
1584
+			case 'T' :
1585
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1586
+					$datetime_value,
1587
+					$this->_fields[ $fieldname ]
1588
+				);
1589
+				break;
1590
+			case 'D' :
1591
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1592
+					$datetime_value,
1593
+					$this->_fields[ $fieldname ]
1594
+				);
1595
+				break;
1596
+			case 'B' :
1597
+				$this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1598
+				break;
1599
+		}
1600
+		$this->_clear_cached_property($fieldname);
1601
+	}
1602
+
1603
+
1604
+	/**
1605
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1606
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1607
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1608
+	 * that could lead to some unexpected results!
1609
+	 *
1610
+	 * @access public
1611
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1612
+	 *                                         value being returned.
1613
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1614
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1615
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1616
+	 * @param string $append                   You can include something to append on the timestamp
1617
+	 * @throws ReflectionException
1618
+	 * @throws InvalidArgumentException
1619
+	 * @throws InvalidInterfaceException
1620
+	 * @throws InvalidDataTypeException
1621
+	 * @throws EE_Error
1622
+	 * @return string timestamp
1623
+	 */
1624
+	public function display_in_my_timezone(
1625
+		$field_name,
1626
+		$callback = 'get_datetime',
1627
+		$args = null,
1628
+		$prepend = '',
1629
+		$append = ''
1630
+	) {
1631
+		$timezone = EEH_DTT_Helper::get_timezone();
1632
+		if ($timezone === $this->_timezone) {
1633
+			return '';
1634
+		}
1635
+		$original_timezone = $this->_timezone;
1636
+		$this->set_timezone($timezone);
1637
+		$fn   = (array) $field_name;
1638
+		$args = array_merge($fn, (array) $args);
1639
+		if (! method_exists($this, $callback)) {
1640
+			throw new EE_Error(
1641
+				sprintf(
1642
+					esc_html__(
1643
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1644
+						'event_espresso'
1645
+					),
1646
+					$callback
1647
+				)
1648
+			);
1649
+		}
1650
+		$args   = (array) $args;
1651
+		$return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1652
+		$this->set_timezone($original_timezone);
1653
+		return $return;
1654
+	}
1655
+
1656
+
1657
+	/**
1658
+	 * Deletes this model object.
1659
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1660
+	 * override
1661
+	 * `EE_Base_Class::_delete` NOT this class.
1662
+	 *
1663
+	 * @return boolean | int
1664
+	 * @throws ReflectionException
1665
+	 * @throws InvalidArgumentException
1666
+	 * @throws InvalidInterfaceException
1667
+	 * @throws InvalidDataTypeException
1668
+	 * @throws EE_Error
1669
+	 */
1670
+	public function delete()
1671
+	{
1672
+		/**
1673
+		 * Called just before the `EE_Base_Class::_delete` method call.
1674
+		 * Note:
1675
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1676
+		 * should be aware that `_delete` may not always result in a permanent delete.
1677
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1678
+		 * soft deletes (trash) the object and does not permanently delete it.
1679
+		 *
1680
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1681
+		 */
1682
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1683
+		$result = $this->_delete();
1684
+		/**
1685
+		 * Called just after the `EE_Base_Class::_delete` method call.
1686
+		 * Note:
1687
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1688
+		 * should be aware that `_delete` may not always result in a permanent delete.
1689
+		 * For example `EE_Soft_Base_Class::_delete`
1690
+		 * soft deletes (trash) the object and does not permanently delete it.
1691
+		 *
1692
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1693
+		 * @param boolean       $result
1694
+		 */
1695
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1696
+		return $result;
1697
+	}
1698
+
1699
+
1700
+	/**
1701
+	 * Calls the specific delete method for the instantiated class.
1702
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1703
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1704
+	 * `EE_Base_Class::delete`
1705
+	 *
1706
+	 * @return bool|int
1707
+	 * @throws ReflectionException
1708
+	 * @throws InvalidArgumentException
1709
+	 * @throws InvalidInterfaceException
1710
+	 * @throws InvalidDataTypeException
1711
+	 * @throws EE_Error
1712
+	 */
1713
+	protected function _delete()
1714
+	{
1715
+		return $this->delete_permanently();
1716
+	}
1717
+
1718
+
1719
+	/**
1720
+	 * Deletes this model object permanently from db
1721
+	 * (but keep in mind related models may block the delete and return an error)
1722
+	 *
1723
+	 * @return bool | int
1724
+	 * @throws ReflectionException
1725
+	 * @throws InvalidArgumentException
1726
+	 * @throws InvalidInterfaceException
1727
+	 * @throws InvalidDataTypeException
1728
+	 * @throws EE_Error
1729
+	 */
1730
+	public function delete_permanently()
1731
+	{
1732
+		/**
1733
+		 * Called just before HARD deleting a model object
1734
+		 *
1735
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1736
+		 */
1737
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1738
+		$model  = $this->get_model();
1739
+		$result = $model->delete_permanently_by_ID($this->ID());
1740
+		$this->refresh_cache_of_related_objects();
1741
+		/**
1742
+		 * Called just after HARD deleting a model object
1743
+		 *
1744
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1745
+		 * @param boolean       $result
1746
+		 */
1747
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1748
+		return $result;
1749
+	}
1750
+
1751
+
1752
+	/**
1753
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1754
+	 * related model objects
1755
+	 *
1756
+	 * @throws ReflectionException
1757
+	 * @throws InvalidArgumentException
1758
+	 * @throws InvalidInterfaceException
1759
+	 * @throws InvalidDataTypeException
1760
+	 * @throws EE_Error
1761
+	 */
1762
+	public function refresh_cache_of_related_objects()
1763
+	{
1764
+		$model = $this->get_model();
1765
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1766
+			if (! empty($this->_model_relations[ $relation_name ])) {
1767
+				$related_objects = $this->_model_relations[ $relation_name ];
1768
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1769
+					//this relation only stores a single model object, not an array
1770
+					//but let's make it consistent
1771
+					$related_objects = array($related_objects);
1772
+				}
1773
+				foreach ($related_objects as $related_object) {
1774
+					//only refresh their cache if they're in memory
1775
+					if ($related_object instanceof EE_Base_Class) {
1776
+						$related_object->clear_cache(
1777
+							$model->get_this_model_name(),
1778
+							$this
1779
+						);
1780
+					}
1781
+				}
1782
+			}
1783
+		}
1784
+	}
1785
+
1786
+
1787
+	/**
1788
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1789
+	 * object just before saving.
1790
+	 *
1791
+	 * @access public
1792
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1793
+	 *                                 if provided during the save() method (often client code will change the fields'
1794
+	 *                                 values before calling save)
1795
+	 * @throws InvalidArgumentException
1796
+	 * @throws InvalidInterfaceException
1797
+	 * @throws InvalidDataTypeException
1798
+	 * @throws EE_Error
1799
+	 * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1800
+	 *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1801
+	 * @throws ReflectionException
1802
+	 * @throws ReflectionException
1803
+	 * @throws ReflectionException
1804
+	 */
1805
+	public function save($set_cols_n_values = array())
1806
+	{
1807
+		$model = $this->get_model();
1808
+		/**
1809
+		 * Filters the fields we're about to save on the model object
1810
+		 *
1811
+		 * @param array         $set_cols_n_values
1812
+		 * @param EE_Base_Class $model_object
1813
+		 */
1814
+		$set_cols_n_values = (array) apply_filters(
1815
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1816
+			$set_cols_n_values,
1817
+			$this
1818
+		);
1819
+		//set attributes as provided in $set_cols_n_values
1820
+		foreach ($set_cols_n_values as $column => $value) {
1821
+			$this->set($column, $value);
1822
+		}
1823
+		// no changes ? then don't do anything
1824
+		if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1825
+			return 0;
1826
+		}
1827
+		/**
1828
+		 * Saving a model object.
1829
+		 * Before we perform a save, this action is fired.
1830
+		 *
1831
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1832
+		 */
1833
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1834
+		if (! $this->allow_persist()) {
1835
+			return 0;
1836
+		}
1837
+		// now get current attribute values
1838
+		$save_cols_n_values = $this->_fields;
1839
+		// if the object already has an ID, update it. Otherwise, insert it
1840
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1841
+		// They have been
1842
+		$old_assumption_concerning_value_preparation = $model
1843
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1844
+		$model->assume_values_already_prepared_by_model_object(true);
1845
+		//does this model have an autoincrement PK?
1846
+		if ($model->has_primary_key_field()) {
1847
+			if ($model->get_primary_key_field()->is_auto_increment()) {
1848
+				//ok check if it's set, if so: update; if not, insert
1849
+				if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1850
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1851
+				} else {
1852
+					unset($save_cols_n_values[ $model->primary_key_name() ]);
1853
+					$results = $model->insert($save_cols_n_values);
1854
+					if ($results) {
1855
+						//if successful, set the primary key
1856
+						//but don't use the normal SET method, because it will check if
1857
+						//an item with the same ID exists in the mapper & db, then
1858
+						//will find it in the db (because we just added it) and THAT object
1859
+						//will get added to the mapper before we can add this one!
1860
+						//but if we just avoid using the SET method, all that headache can be avoided
1861
+						$pk_field_name                   = $model->primary_key_name();
1862
+						$this->_fields[ $pk_field_name ] = $results;
1863
+						$this->_clear_cached_property($pk_field_name);
1864
+						$model->add_to_entity_map($this);
1865
+						$this->_update_cached_related_model_objs_fks();
1866
+					}
1867
+				}
1868
+			} else {//PK is NOT auto-increment
1869
+				//so check if one like it already exists in the db
1870
+				if ($model->exists_by_ID($this->ID())) {
1871
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1872
+						throw new EE_Error(
1873
+							sprintf(
1874
+								esc_html__(
1875
+									'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',
1876
+									'event_espresso'
1877
+								),
1878
+								get_class($this),
1879
+								get_class($model) . '::instance()->add_to_entity_map()',
1880
+								get_class($model) . '::instance()->get_one_by_ID()',
1881
+								'<br />'
1882
+							)
1883
+						);
1884
+					}
1885
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1886
+				} else {
1887
+					$results = $model->insert($save_cols_n_values);
1888
+					$this->_update_cached_related_model_objs_fks();
1889
+				}
1890
+			}
1891
+		} else {//there is NO primary key
1892
+			$already_in_db = false;
1893
+			foreach ($model->unique_indexes() as $index) {
1894
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1895
+				if ($model->exists(array($uniqueness_where_params))) {
1896
+					$already_in_db = true;
1897
+				}
1898
+			}
1899
+			if ($already_in_db) {
1900
+				$combined_pk_fields_n_values = array_intersect_key($save_cols_n_values,
1901
+					$model->get_combined_primary_key_fields());
1902
+				$results                     = $model->update(
1903
+					$save_cols_n_values,
1904
+					$combined_pk_fields_n_values
1905
+				);
1906
+			} else {
1907
+				$results = $model->insert($save_cols_n_values);
1908
+			}
1909
+		}
1910
+		//restore the old assumption about values being prepared by the model object
1911
+		$model->assume_values_already_prepared_by_model_object(
1912
+				$old_assumption_concerning_value_preparation
1913
+			);
1914
+		/**
1915
+		 * After saving the model object this action is called
1916
+		 *
1917
+		 * @param EE_Base_Class $model_object which was just saved
1918
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1919
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1920
+		 */
1921
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1922
+		$this->_has_changes = false;
1923
+		return $results;
1924
+	}
1925
+
1926
+
1927
+	/**
1928
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1929
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1930
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1931
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1932
+	 * 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
1933
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1934
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1935
+	 *
1936
+	 * @return void
1937
+	 * @throws ReflectionException
1938
+	 * @throws InvalidArgumentException
1939
+	 * @throws InvalidInterfaceException
1940
+	 * @throws InvalidDataTypeException
1941
+	 * @throws EE_Error
1942
+	 */
1943
+	protected function _update_cached_related_model_objs_fks()
1944
+	{
1945
+		$model = $this->get_model();
1946
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1947
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1948
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1949
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1950
+						$model->get_this_model_name()
1951
+					);
1952
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1953
+					if ($related_model_obj_in_cache->ID()) {
1954
+						$related_model_obj_in_cache->save();
1955
+					}
1956
+				}
1957
+			}
1958
+		}
1959
+	}
1960
+
1961
+
1962
+	/**
1963
+	 * Saves this model object and its NEW cached relations to the database.
1964
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1965
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1966
+	 * because otherwise, there's a potential for infinite looping of saving
1967
+	 * Saves the cached related model objects, and ensures the relation between them
1968
+	 * and this object and properly setup
1969
+	 *
1970
+	 * @return int ID of new model object on save; 0 on failure+
1971
+	 * @throws ReflectionException
1972
+	 * @throws InvalidArgumentException
1973
+	 * @throws InvalidInterfaceException
1974
+	 * @throws InvalidDataTypeException
1975
+	 * @throws EE_Error
1976
+	 */
1977
+	public function save_new_cached_related_model_objs()
1978
+	{
1979
+		//make sure this has been saved
1980
+		if (! $this->ID()) {
1981
+			$id = $this->save();
1982
+		} else {
1983
+			$id = $this->ID();
1984
+		}
1985
+		//now save all the NEW cached model objects  (ie they don't exist in the DB)
1986
+		foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1987
+			if ($this->_model_relations[ $relationName ]) {
1988
+				//is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1989
+				//or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1990
+				/* @var $related_model_obj EE_Base_Class */
1991
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
1992
+					//add a relation to that relation type (which saves the appropriate thing in the process)
1993
+					//but ONLY if it DOES NOT exist in the DB
1994
+					$related_model_obj = $this->_model_relations[ $relationName ];
1995
+					//					if( ! $related_model_obj->ID()){
1996
+					$this->_add_relation_to($related_model_obj, $relationName);
1997
+					$related_model_obj->save_new_cached_related_model_objs();
1998
+					//					}
1999
+				} else {
2000
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2001
+						//add a relation to that relation type (which saves the appropriate thing in the process)
2002
+						//but ONLY if it DOES NOT exist in the DB
2003
+						//						if( ! $related_model_obj->ID()){
2004
+						$this->_add_relation_to($related_model_obj, $relationName);
2005
+						$related_model_obj->save_new_cached_related_model_objs();
2006
+						//						}
2007
+					}
2008
+				}
2009
+			}
2010
+		}
2011
+		return $id;
2012
+	}
2013
+
2014
+
2015
+	/**
2016
+	 * for getting a model while instantiated.
2017
+	 *
2018
+	 * @return EEM_Base | EEM_CPT_Base
2019
+	 * @throws ReflectionException
2020
+	 * @throws InvalidArgumentException
2021
+	 * @throws InvalidInterfaceException
2022
+	 * @throws InvalidDataTypeException
2023
+	 * @throws EE_Error
2024
+	 */
2025
+	public function get_model()
2026
+	{
2027
+		if (! $this->_model) {
2028
+			$modelName    = self::_get_model_classname(get_class($this));
2029
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2030
+		} else {
2031
+			$this->_model->set_timezone($this->_timezone);
2032
+		}
2033
+		return $this->_model;
2034
+	}
2035
+
2036
+
2037
+	/**
2038
+	 * @param $props_n_values
2039
+	 * @param $classname
2040
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2041
+	 * @throws ReflectionException
2042
+	 * @throws InvalidArgumentException
2043
+	 * @throws InvalidInterfaceException
2044
+	 * @throws InvalidDataTypeException
2045
+	 * @throws EE_Error
2046
+	 */
2047
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2048
+	{
2049
+		//TODO: will not work for Term_Relationships because they have no PK!
2050
+		$primary_id_ref = self::_get_primary_key_name($classname);
2051
+		if (
2052
+			array_key_exists($primary_id_ref, $props_n_values)
2053
+			&& ! empty($props_n_values[ $primary_id_ref ])
2054
+		) {
2055
+			$id = $props_n_values[ $primary_id_ref ];
2056
+			return self::_get_model($classname)->get_from_entity_map($id);
2057
+		}
2058
+		return false;
2059
+	}
2060
+
2061
+
2062
+	/**
2063
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2064
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2065
+	 * 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
2066
+	 * we return false.
2067
+	 *
2068
+	 * @param  array  $props_n_values   incoming array of properties and their values
2069
+	 * @param  string $classname        the classname of the child class
2070
+	 * @param null    $timezone
2071
+	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
2072
+	 *                                  date_format and the second value is the time format
2073
+	 * @return mixed (EE_Base_Class|bool)
2074
+	 * @throws InvalidArgumentException
2075
+	 * @throws InvalidInterfaceException
2076
+	 * @throws InvalidDataTypeException
2077
+	 * @throws EE_Error
2078
+	 * @throws ReflectionException
2079
+	 * @throws ReflectionException
2080
+	 * @throws ReflectionException
2081
+	 */
2082
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2083
+	{
2084
+		$existing = null;
2085
+		$model    = self::_get_model($classname, $timezone);
2086
+		if ($model->has_primary_key_field()) {
2087
+			$primary_id_ref = self::_get_primary_key_name($classname);
2088
+			if (array_key_exists($primary_id_ref, $props_n_values)
2089
+				&& ! empty($props_n_values[ $primary_id_ref ])
2090
+			) {
2091
+				$existing = $model->get_one_by_ID(
2092
+					$props_n_values[ $primary_id_ref ]
2093
+				);
2094
+			}
2095
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2096
+			//no primary key on this model, but there's still a matching item in the DB
2097
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2098
+				self::_get_model($classname, $timezone)
2099
+					->get_index_primary_key_string($props_n_values)
2100
+			);
2101
+		}
2102
+		if ($existing) {
2103
+			//set date formats if present before setting values
2104
+			if (! empty($date_formats) && is_array($date_formats)) {
2105
+				$existing->set_date_format($date_formats[0]);
2106
+				$existing->set_time_format($date_formats[1]);
2107
+			} else {
2108
+				//set default formats for date and time
2109
+				$existing->set_date_format(get_option('date_format'));
2110
+				$existing->set_time_format(get_option('time_format'));
2111
+			}
2112
+			foreach ($props_n_values as $property => $field_value) {
2113
+				$existing->set($property, $field_value);
2114
+			}
2115
+			return $existing;
2116
+		}
2117
+		return false;
2118
+	}
2119
+
2120
+
2121
+	/**
2122
+	 * Gets the EEM_*_Model for this class
2123
+	 *
2124
+	 * @access public now, as this is more convenient
2125
+	 * @param      $classname
2126
+	 * @param null $timezone
2127
+	 * @throws ReflectionException
2128
+	 * @throws InvalidArgumentException
2129
+	 * @throws InvalidInterfaceException
2130
+	 * @throws InvalidDataTypeException
2131
+	 * @throws EE_Error
2132
+	 * @return EEM_Base
2133
+	 */
2134
+	protected static function _get_model($classname, $timezone = null)
2135
+	{
2136
+		//find model for this class
2137
+		if (! $classname) {
2138
+			throw new EE_Error(
2139
+				sprintf(
2140
+					esc_html__(
2141
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2142
+						'event_espresso'
2143
+					),
2144
+					$classname
2145
+				)
2146
+			);
2147
+		}
2148
+		$modelName = self::_get_model_classname($classname);
2149
+		return self::_get_model_instance_with_name($modelName, $timezone);
2150
+	}
2151
+
2152
+
2153
+	/**
2154
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2155
+	 *
2156
+	 * @param string $model_classname
2157
+	 * @param null   $timezone
2158
+	 * @return EEM_Base
2159
+	 * @throws ReflectionException
2160
+	 * @throws InvalidArgumentException
2161
+	 * @throws InvalidInterfaceException
2162
+	 * @throws InvalidDataTypeException
2163
+	 * @throws EE_Error
2164
+	 */
2165
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2166
+	{
2167
+		$model_classname = str_replace('EEM_', '', $model_classname);
2168
+		$model           = EE_Registry::instance()->load_model($model_classname);
2169
+		$model->set_timezone($timezone);
2170
+		return $model;
2171
+	}
2172
+
2173
+
2174
+	/**
2175
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2176
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2177
+	 *
2178
+	 * @param null $model_name
2179
+	 * @return string like EEM_Attendee
2180
+	 */
2181
+	private static function _get_model_classname($model_name = null)
2182
+	{
2183
+		if (strpos($model_name, 'EE_') === 0) {
2184
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
2185
+		} else {
2186
+			$model_classname = 'EEM_' . $model_name;
2187
+		}
2188
+		return $model_classname;
2189
+	}
2190
+
2191
+
2192
+	/**
2193
+	 * returns the name of the primary key attribute
2194
+	 *
2195
+	 * @param null $classname
2196
+	 * @throws ReflectionException
2197
+	 * @throws InvalidArgumentException
2198
+	 * @throws InvalidInterfaceException
2199
+	 * @throws InvalidDataTypeException
2200
+	 * @throws EE_Error
2201
+	 * @return string
2202
+	 */
2203
+	protected static function _get_primary_key_name($classname = null)
2204
+	{
2205
+		if (! $classname) {
2206
+			throw new EE_Error(
2207
+				sprintf(
2208
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2209
+					$classname
2210
+				)
2211
+			);
2212
+		}
2213
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2214
+	}
2215
+
2216
+
2217
+	/**
2218
+	 * Gets the value of the primary key.
2219
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2220
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2221
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2222
+	 *
2223
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2224
+	 * @throws ReflectionException
2225
+	 * @throws InvalidArgumentException
2226
+	 * @throws InvalidInterfaceException
2227
+	 * @throws InvalidDataTypeException
2228
+	 * @throws EE_Error
2229
+	 */
2230
+	public function ID()
2231
+	{
2232
+		$model = $this->get_model();
2233
+		//now that we know the name of the variable, use a variable variable to get its value and return its
2234
+		if ($model->has_primary_key_field()) {
2235
+			return $this->_fields[ $model->primary_key_name() ];
2236
+		}
2237
+		return $model->get_index_primary_key_string($this->_fields);
2238
+	}
2239
+
2240
+
2241
+	/**
2242
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2243
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2244
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2245
+	 *
2246
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2247
+	 * @param string $relationName                     eg 'Events','Question',etc.
2248
+	 *                                                 an attendee to a group, you also want to specify which role they
2249
+	 *                                                 will have in that group. So you would use this parameter to
2250
+	 *                                                 specify array('role-column-name'=>'role-id')
2251
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2252
+	 *                                                 allow you to further constrict the relation to being added.
2253
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2254
+	 *                                                 column on the JOIN table and currently only the HABTM models
2255
+	 *                                                 accept these additional conditions.  Also remember that if an
2256
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2257
+	 *                                                 NEW row is created in the join table.
2258
+	 * @param null   $cache_id
2259
+	 * @throws ReflectionException
2260
+	 * @throws InvalidArgumentException
2261
+	 * @throws InvalidInterfaceException
2262
+	 * @throws InvalidDataTypeException
2263
+	 * @throws EE_Error
2264
+	 * @return EE_Base_Class the object the relation was added to
2265
+	 */
2266
+	public function _add_relation_to(
2267
+		$otherObjectModelObjectOrID,
2268
+		$relationName,
2269
+		$extra_join_model_fields_n_values = array(),
2270
+		$cache_id = null
2271
+	) {
2272
+		$model = $this->get_model();
2273
+		//if this thing exists in the DB, save the relation to the DB
2274
+		if ($this->ID()) {
2275
+			$otherObject = $model->add_relationship_to(
2276
+				$this,
2277
+				$otherObjectModelObjectOrID,
2278
+				$relationName,
2279
+				$extra_join_model_fields_n_values
2280
+			);
2281
+			//clear cache so future get_many_related and get_first_related() return new results.
2282
+			$this->clear_cache($relationName, $otherObject, true);
2283
+			if ($otherObject instanceof EE_Base_Class) {
2284
+				$otherObject->clear_cache($model->get_this_model_name(), $this);
2285
+			}
2286
+		} else {
2287
+			//this thing doesn't exist in the DB,  so just cache it
2288
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2289
+				throw new EE_Error(
2290
+					sprintf(
2291
+						esc_html__(
2292
+							'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',
2293
+							'event_espresso'
2294
+						),
2295
+						$otherObjectModelObjectOrID,
2296
+						get_class($this)
2297
+					)
2298
+				);
2299
+			}
2300
+			$otherObject = $otherObjectModelObjectOrID;
2301
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2302
+		}
2303
+		if ($otherObject instanceof EE_Base_Class) {
2304
+			//fix the reciprocal relation too
2305
+			if ($otherObject->ID()) {
2306
+				//its saved so assumed relations exist in the DB, so we can just
2307
+				//clear the cache so future queries use the updated info in the DB
2308
+				$otherObject->clear_cache(
2309
+					$model->get_this_model_name(),
2310
+					null,
2311
+					true
2312
+				);
2313
+			} else {
2314
+				//it's not saved, so it caches relations like this
2315
+				$otherObject->cache($model->get_this_model_name(), $this);
2316
+			}
2317
+		}
2318
+		return $otherObject;
2319
+	}
2320
+
2321
+
2322
+	/**
2323
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2324
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2325
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2326
+	 * from the cache
2327
+	 *
2328
+	 * @param mixed  $otherObjectModelObjectOrID
2329
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2330
+	 *                to the DB yet
2331
+	 * @param string $relationName
2332
+	 * @param array  $where_query
2333
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2334
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2335
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2336
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2337
+	 *                created in the join table.
2338
+	 * @return EE_Base_Class the relation was removed from
2339
+	 * @throws ReflectionException
2340
+	 * @throws InvalidArgumentException
2341
+	 * @throws InvalidInterfaceException
2342
+	 * @throws InvalidDataTypeException
2343
+	 * @throws EE_Error
2344
+	 */
2345
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2346
+	{
2347
+		if ($this->ID()) {
2348
+			//if this exists in the DB, save the relation change to the DB too
2349
+			$otherObject = $this->get_model()->remove_relationship_to(
2350
+				$this,
2351
+				$otherObjectModelObjectOrID,
2352
+				$relationName,
2353
+				$where_query
2354
+			);
2355
+			$this->clear_cache(
2356
+				$relationName,
2357
+				$otherObject
2358
+			);
2359
+		} else {
2360
+			//this doesn't exist in the DB, just remove it from the cache
2361
+			$otherObject = $this->clear_cache(
2362
+				$relationName,
2363
+				$otherObjectModelObjectOrID
2364
+			);
2365
+		}
2366
+		if ($otherObject instanceof EE_Base_Class) {
2367
+			$otherObject->clear_cache(
2368
+				$this->get_model()->get_this_model_name(),
2369
+				$this
2370
+			);
2371
+		}
2372
+		return $otherObject;
2373
+	}
2374
+
2375
+
2376
+	/**
2377
+	 * Removes ALL the related things for the $relationName.
2378
+	 *
2379
+	 * @param string $relationName
2380
+	 * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
2381
+	 * @return EE_Base_Class
2382
+	 * @throws ReflectionException
2383
+	 * @throws InvalidArgumentException
2384
+	 * @throws InvalidInterfaceException
2385
+	 * @throws InvalidDataTypeException
2386
+	 * @throws EE_Error
2387
+	 */
2388
+	public function _remove_relations($relationName, $where_query_params = array())
2389
+	{
2390
+		if ($this->ID()) {
2391
+			//if this exists in the DB, save the relation change to the DB too
2392
+			$otherObjects = $this->get_model()->remove_relations(
2393
+				$this,
2394
+				$relationName,
2395
+				$where_query_params
2396
+			);
2397
+			$this->clear_cache(
2398
+				$relationName,
2399
+				null,
2400
+				true
2401
+			);
2402
+		} else {
2403
+			//this doesn't exist in the DB, just remove it from the cache
2404
+			$otherObjects = $this->clear_cache(
2405
+				$relationName,
2406
+				null,
2407
+				true
2408
+			);
2409
+		}
2410
+		if (is_array($otherObjects)) {
2411
+			foreach ($otherObjects as $otherObject) {
2412
+				$otherObject->clear_cache(
2413
+					$this->get_model()->get_this_model_name(),
2414
+					$this
2415
+				);
2416
+			}
2417
+		}
2418
+		return $otherObjects;
2419
+	}
2420
+
2421
+
2422
+	/**
2423
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2424
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2425
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2426
+	 * because we want to get even deleted items etc.
2427
+	 *
2428
+	 * @param string $relationName key in the model's _model_relations array
2429
+	 * @param array  $query_params like EEM_Base::get_all
2430
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2431
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2432
+	 *                             results if you want IDs
2433
+	 * @throws ReflectionException
2434
+	 * @throws InvalidArgumentException
2435
+	 * @throws InvalidInterfaceException
2436
+	 * @throws InvalidDataTypeException
2437
+	 * @throws EE_Error
2438
+	 */
2439
+	public function get_many_related($relationName, $query_params = array())
2440
+	{
2441
+		if ($this->ID()) {
2442
+			//this exists in the DB, so get the related things from either the cache or the DB
2443
+			//if there are query parameters, forget about caching the related model objects.
2444
+			if ($query_params) {
2445
+				$related_model_objects = $this->get_model()->get_all_related(
2446
+					$this,
2447
+					$relationName,
2448
+					$query_params
2449
+				);
2450
+			} else {
2451
+				//did we already cache the result of this query?
2452
+				$cached_results = $this->get_all_from_cache($relationName);
2453
+				if (! $cached_results) {
2454
+					$related_model_objects = $this->get_model()->get_all_related(
2455
+						$this,
2456
+						$relationName,
2457
+						$query_params
2458
+					);
2459
+					//if no query parameters were passed, then we got all the related model objects
2460
+					//for that relation. We can cache them then.
2461
+					foreach ($related_model_objects as $related_model_object) {
2462
+						$this->cache($relationName, $related_model_object);
2463
+					}
2464
+				} else {
2465
+					$related_model_objects = $cached_results;
2466
+				}
2467
+			}
2468
+		} else {
2469
+			//this doesn't exist in the DB, so just get the related things from the cache
2470
+			$related_model_objects = $this->get_all_from_cache($relationName);
2471
+		}
2472
+		return $related_model_objects;
2473
+	}
2474
+
2475
+
2476
+	/**
2477
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2478
+	 * unless otherwise specified in the $query_params
2479
+	 *
2480
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2481
+	 * @param array  $query_params   like EEM_Base::get_all's
2482
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2483
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2484
+	 *                               that by the setting $distinct to TRUE;
2485
+	 * @return int
2486
+	 * @throws ReflectionException
2487
+	 * @throws InvalidArgumentException
2488
+	 * @throws InvalidInterfaceException
2489
+	 * @throws InvalidDataTypeException
2490
+	 * @throws EE_Error
2491
+	 */
2492
+	public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2493
+	{
2494
+		return $this->get_model()->count_related(
2495
+			$this,
2496
+			$relation_name,
2497
+			$query_params,
2498
+			$field_to_count,
2499
+			$distinct
2500
+		);
2501
+	}
2502
+
2503
+
2504
+	/**
2505
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2506
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2507
+	 *
2508
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2509
+	 * @param array  $query_params  like EEM_Base::get_all's
2510
+	 * @param string $field_to_sum  name of field to count by.
2511
+	 *                              By default, uses primary key
2512
+	 *                              (which doesn't make much sense, so you should probably change it)
2513
+	 * @return int
2514
+	 * @throws ReflectionException
2515
+	 * @throws InvalidArgumentException
2516
+	 * @throws InvalidInterfaceException
2517
+	 * @throws InvalidDataTypeException
2518
+	 * @throws EE_Error
2519
+	 */
2520
+	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2521
+	{
2522
+		return $this->get_model()->sum_related(
2523
+			$this,
2524
+			$relation_name,
2525
+			$query_params,
2526
+			$field_to_sum
2527
+		);
2528
+	}
2529
+
2530
+
2531
+	/**
2532
+	 * Gets the first (ie, one) related model object of the specified type.
2533
+	 *
2534
+	 * @param string $relationName key in the model's _model_relations array
2535
+	 * @param array  $query_params like EEM_Base::get_all
2536
+	 * @return EE_Base_Class (not an array, a single object)
2537
+	 * @throws ReflectionException
2538
+	 * @throws InvalidArgumentException
2539
+	 * @throws InvalidInterfaceException
2540
+	 * @throws InvalidDataTypeException
2541
+	 * @throws EE_Error
2542
+	 */
2543
+	public function get_first_related($relationName, $query_params = array())
2544
+	{
2545
+		$model = $this->get_model();
2546
+		if ($this->ID()) {//this exists in the DB, get from the cache OR the DB
2547
+			//if they've provided some query parameters, don't bother trying to cache the result
2548
+			//also make sure we're not caching the result of get_first_related
2549
+			//on a relation which should have an array of objects (because the cache might have an array of objects)
2550
+			if ($query_params
2551
+				|| ! $model->related_settings_for($relationName)
2552
+					 instanceof
2553
+					 EE_Belongs_To_Relation
2554
+			) {
2555
+				$related_model_object = $model->get_first_related(
2556
+					$this,
2557
+					$relationName,
2558
+					$query_params
2559
+				);
2560
+			} else {
2561
+				//first, check if we've already cached the result of this query
2562
+				$cached_result = $this->get_one_from_cache($relationName);
2563
+				if (! $cached_result) {
2564
+					$related_model_object = $model->get_first_related(
2565
+						$this,
2566
+						$relationName,
2567
+						$query_params
2568
+					);
2569
+					$this->cache($relationName, $related_model_object);
2570
+				} else {
2571
+					$related_model_object = $cached_result;
2572
+				}
2573
+			}
2574
+		} else {
2575
+			$related_model_object = null;
2576
+			// this doesn't exist in the Db,
2577
+			// but maybe the relation is of type belongs to, and so the related thing might
2578
+			if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2579
+				$related_model_object = $model->get_first_related(
2580
+					$this,
2581
+					$relationName,
2582
+					$query_params
2583
+				);
2584
+			}
2585
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2586
+			// just get what's cached on this object
2587
+			if (! $related_model_object) {
2588
+				$related_model_object = $this->get_one_from_cache($relationName);
2589
+			}
2590
+		}
2591
+		return $related_model_object;
2592
+	}
2593
+
2594
+
2595
+	/**
2596
+	 * Does a delete on all related objects of type $relationName and removes
2597
+	 * the current model object's relation to them. If they can't be deleted (because
2598
+	 * of blocking related model objects) does nothing. If the related model objects are
2599
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2600
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2601
+	 *
2602
+	 * @param string $relationName
2603
+	 * @param array  $query_params like EEM_Base::get_all's
2604
+	 * @return int how many deleted
2605
+	 * @throws ReflectionException
2606
+	 * @throws InvalidArgumentException
2607
+	 * @throws InvalidInterfaceException
2608
+	 * @throws InvalidDataTypeException
2609
+	 * @throws EE_Error
2610
+	 */
2611
+	public function delete_related($relationName, $query_params = array())
2612
+	{
2613
+		if ($this->ID()) {
2614
+			$count = $this->get_model()->delete_related(
2615
+				$this,
2616
+				$relationName,
2617
+				$query_params
2618
+			);
2619
+		} else {
2620
+			$count = count($this->get_all_from_cache($relationName));
2621
+			$this->clear_cache($relationName, null, true);
2622
+		}
2623
+		return $count;
2624
+	}
2625
+
2626
+
2627
+	/**
2628
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2629
+	 * the current model object's relation to them. If they can't be deleted (because
2630
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2631
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2632
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2633
+	 *
2634
+	 * @param string $relationName
2635
+	 * @param array  $query_params like EEM_Base::get_all's
2636
+	 * @return int how many deleted (including those soft deleted)
2637
+	 * @throws ReflectionException
2638
+	 * @throws InvalidArgumentException
2639
+	 * @throws InvalidInterfaceException
2640
+	 * @throws InvalidDataTypeException
2641
+	 * @throws EE_Error
2642
+	 */
2643
+	public function delete_related_permanently($relationName, $query_params = array())
2644
+	{
2645
+		if ($this->ID()) {
2646
+			$count = $this->get_model()->delete_related_permanently(
2647
+				$this,
2648
+				$relationName,
2649
+				$query_params
2650
+			);
2651
+		} else {
2652
+			$count = count($this->get_all_from_cache($relationName));
2653
+		}
2654
+		$this->clear_cache($relationName, null, true);
2655
+		return $count;
2656
+	}
2657
+
2658
+
2659
+	/**
2660
+	 * is_set
2661
+	 * Just a simple utility function children can use for checking if property exists
2662
+	 *
2663
+	 * @access  public
2664
+	 * @param  string $field_name property to check
2665
+	 * @return bool                              TRUE if existing,FALSE if not.
2666
+	 */
2667
+	public function is_set($field_name)
2668
+	{
2669
+		return isset($this->_fields[ $field_name ]);
2670
+	}
2671
+
2672
+
2673
+	/**
2674
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2675
+	 * EE_Error exception if they don't
2676
+	 *
2677
+	 * @param  mixed (string|array) $properties properties to check
2678
+	 * @throws EE_Error
2679
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2680
+	 */
2681
+	protected function _property_exists($properties)
2682
+	{
2683
+		foreach ((array) $properties as $property_name) {
2684
+			//first make sure this property exists
2685
+			if (! $this->_fields[ $property_name ]) {
2686
+				throw new EE_Error(
2687
+					sprintf(
2688
+						esc_html__(
2689
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2690
+							'event_espresso'
2691
+						),
2692
+						$property_name
2693
+					)
2694
+				);
2695
+			}
2696
+		}
2697
+		return true;
2698
+	}
2699
+
2700
+
2701
+	/**
2702
+	 * This simply returns an array of model fields for this object
2703
+	 *
2704
+	 * @return array
2705
+	 * @throws ReflectionException
2706
+	 * @throws InvalidArgumentException
2707
+	 * @throws InvalidInterfaceException
2708
+	 * @throws InvalidDataTypeException
2709
+	 * @throws EE_Error
2710
+	 */
2711
+	public function model_field_array()
2712
+	{
2713
+		$fields     = $this->get_model()->field_settings(false);
2714
+		$properties = array();
2715
+		//remove prepended underscore
2716
+		foreach ($fields as $field_name => $settings) {
2717
+			$properties[ $field_name ] = $this->get($field_name);
2718
+		}
2719
+		return $properties;
2720
+	}
2721
+
2722
+
2723
+	/**
2724
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2725
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2726
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2727
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2728
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2729
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2730
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2731
+	 * and accepts 2 arguments: the object on which the function was called,
2732
+	 * and an array of the original arguments passed to the function.
2733
+	 * Whatever their callback function returns will be returned by this function.
2734
+	 * Example: in functions.php (or in a plugin):
2735
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2736
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2737
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2738
+	 *          return $previousReturnValue.$returnString;
2739
+	 *      }
2740
+	 * require('EE_Answer.class.php');
2741
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2742
+	 * echo $answer->my_callback('monkeys',100);
2743
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2744
+	 *
2745
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2746
+	 * @param array  $args       array of original arguments passed to the function
2747
+	 * @throws EE_Error
2748
+	 * @return mixed whatever the plugin which calls add_filter decides
2749
+	 */
2750
+	public function __call($methodName, $args)
2751
+	{
2752
+		$className = get_class($this);
2753
+		$tagName   = "FHEE__{$className}__{$methodName}";
2754
+		if (! has_filter($tagName)) {
2755
+			throw new EE_Error(
2756
+				sprintf(
2757
+					esc_html__(
2758
+						"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;}",
2759
+						'event_espresso'
2760
+					),
2761
+					$methodName,
2762
+					$className,
2763
+					$tagName
2764
+				)
2765
+			);
2766
+		}
2767
+		return apply_filters($tagName, null, $this, $args);
2768
+	}
2769
+
2770
+
2771
+	/**
2772
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2773
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2774
+	 *
2775
+	 * @param string $meta_key
2776
+	 * @param mixed  $meta_value
2777
+	 * @param mixed  $previous_value
2778
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2779
+	 *                  NOTE: if the values haven't changed, returns 0
2780
+	 * @throws InvalidArgumentException
2781
+	 * @throws InvalidInterfaceException
2782
+	 * @throws InvalidDataTypeException
2783
+	 * @throws EE_Error
2784
+	 * @throws ReflectionException
2785
+	 */
2786
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2787
+	{
2788
+		$query_params = array(
2789
+			array(
2790
+				'EXM_key'  => $meta_key,
2791
+				'OBJ_ID'   => $this->ID(),
2792
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2793
+			),
2794
+		);
2795
+		if ($previous_value !== null) {
2796
+			$query_params[0]['EXM_value'] = $meta_value;
2797
+		}
2798
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2799
+		if (! $existing_rows_like_that) {
2800
+			return $this->add_extra_meta($meta_key, $meta_value);
2801
+		}
2802
+		foreach ($existing_rows_like_that as $existing_row) {
2803
+			$existing_row->save(array('EXM_value' => $meta_value));
2804
+		}
2805
+		return count($existing_rows_like_that);
2806
+	}
2807
+
2808
+
2809
+	/**
2810
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2811
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2812
+	 * extra meta row was entered, false if not
2813
+	 *
2814
+	 * @param string  $meta_key
2815
+	 * @param mixed   $meta_value
2816
+	 * @param boolean $unique
2817
+	 * @return boolean
2818
+	 * @throws InvalidArgumentException
2819
+	 * @throws InvalidInterfaceException
2820
+	 * @throws InvalidDataTypeException
2821
+	 * @throws EE_Error
2822
+	 * @throws ReflectionException
2823
+	 * @throws ReflectionException
2824
+	 */
2825
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2826
+	{
2827
+		if ($unique) {
2828
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2829
+				array(
2830
+					array(
2831
+						'EXM_key'  => $meta_key,
2832
+						'OBJ_ID'   => $this->ID(),
2833
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2834
+					),
2835
+				)
2836
+			);
2837
+			if ($existing_extra_meta) {
2838
+				return false;
2839
+			}
2840
+		}
2841
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2842
+			array(
2843
+				'EXM_key'   => $meta_key,
2844
+				'EXM_value' => $meta_value,
2845
+				'OBJ_ID'    => $this->ID(),
2846
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2847
+			)
2848
+		);
2849
+		$new_extra_meta->save();
2850
+		return true;
2851
+	}
2852
+
2853
+
2854
+	/**
2855
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2856
+	 * is specified, only deletes extra meta records with that value.
2857
+	 *
2858
+	 * @param string $meta_key
2859
+	 * @param mixed  $meta_value
2860
+	 * @return int number of extra meta rows deleted
2861
+	 * @throws InvalidArgumentException
2862
+	 * @throws InvalidInterfaceException
2863
+	 * @throws InvalidDataTypeException
2864
+	 * @throws EE_Error
2865
+	 * @throws ReflectionException
2866
+	 */
2867
+	public function delete_extra_meta($meta_key, $meta_value = null)
2868
+	{
2869
+		$query_params = array(
2870
+			array(
2871
+				'EXM_key'  => $meta_key,
2872
+				'OBJ_ID'   => $this->ID(),
2873
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2874
+			),
2875
+		);
2876
+		if ($meta_value !== null) {
2877
+			$query_params[0]['EXM_value'] = $meta_value;
2878
+		}
2879
+		return EEM_Extra_Meta::instance()->delete($query_params);
2880
+	}
2881
+
2882
+
2883
+	/**
2884
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2885
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2886
+	 * You can specify $default is case you haven't found the extra meta
2887
+	 *
2888
+	 * @param string  $meta_key
2889
+	 * @param boolean $single
2890
+	 * @param mixed   $default if we don't find anything, what should we return?
2891
+	 * @return mixed single value if $single; array if ! $single
2892
+	 * @throws ReflectionException
2893
+	 * @throws InvalidArgumentException
2894
+	 * @throws InvalidInterfaceException
2895
+	 * @throws InvalidDataTypeException
2896
+	 * @throws EE_Error
2897
+	 */
2898
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2899
+	{
2900
+		if ($single) {
2901
+			$result = $this->get_first_related(
2902
+				'Extra_Meta',
2903
+				array(array('EXM_key' => $meta_key))
2904
+			);
2905
+			if ($result instanceof EE_Extra_Meta) {
2906
+				return $result->value();
2907
+			}
2908
+		} else {
2909
+			$results = $this->get_many_related(
2910
+				'Extra_Meta',
2911
+				array(array('EXM_key' => $meta_key))
2912
+			);
2913
+			if ($results) {
2914
+				$values = array();
2915
+				foreach ($results as $result) {
2916
+					if ($result instanceof EE_Extra_Meta) {
2917
+						$values[ $result->ID() ] = $result->value();
2918
+					}
2919
+				}
2920
+				return $values;
2921
+			}
2922
+		}
2923
+		//if nothing discovered yet return default.
2924
+		return apply_filters(
2925
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2926
+			$default,
2927
+			$meta_key,
2928
+			$single,
2929
+			$this
2930
+		);
2931
+	}
2932
+
2933
+
2934
+	/**
2935
+	 * Returns a simple array of all the extra meta associated with this model object.
2936
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2937
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2938
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2939
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2940
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2941
+	 * finally the extra meta's value as each sub-value. (eg
2942
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2943
+	 *
2944
+	 * @param boolean $one_of_each_key
2945
+	 * @return array
2946
+	 * @throws ReflectionException
2947
+	 * @throws InvalidArgumentException
2948
+	 * @throws InvalidInterfaceException
2949
+	 * @throws InvalidDataTypeException
2950
+	 * @throws EE_Error
2951
+	 */
2952
+	public function all_extra_meta_array($one_of_each_key = true)
2953
+	{
2954
+		$return_array = array();
2955
+		if ($one_of_each_key) {
2956
+			$extra_meta_objs = $this->get_many_related(
2957
+				'Extra_Meta',
2958
+				array('group_by' => 'EXM_key')
2959
+			);
2960
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2961
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2962
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2963
+				}
2964
+			}
2965
+		} else {
2966
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2967
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2968
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2969
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2970
+						$return_array[ $extra_meta_obj->key() ] = array();
2971
+					}
2972
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2973
+				}
2974
+			}
2975
+		}
2976
+		return $return_array;
2977
+	}
2978
+
2979
+
2980
+	/**
2981
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
2982
+	 *
2983
+	 * @return string
2984
+	 * @throws ReflectionException
2985
+	 * @throws InvalidArgumentException
2986
+	 * @throws InvalidInterfaceException
2987
+	 * @throws InvalidDataTypeException
2988
+	 * @throws EE_Error
2989
+	 */
2990
+	public function name()
2991
+	{
2992
+		//find a field that's not a text field
2993
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2994
+		if ($field_we_can_use) {
2995
+			return $this->get($field_we_can_use->get_name());
2996
+		}
2997
+		$first_few_properties = $this->model_field_array();
2998
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
2999
+		$name_parts           = array();
3000
+		foreach ($first_few_properties as $name => $value) {
3001
+			$name_parts[] = "$name:$value";
3002
+		}
3003
+		return implode(',', $name_parts);
3004
+	}
3005
+
3006
+
3007
+	/**
3008
+	 * in_entity_map
3009
+	 * Checks if this model object has been proven to already be in the entity map
3010
+	 *
3011
+	 * @return boolean
3012
+	 * @throws ReflectionException
3013
+	 * @throws InvalidArgumentException
3014
+	 * @throws InvalidInterfaceException
3015
+	 * @throws InvalidDataTypeException
3016
+	 * @throws EE_Error
3017
+	 */
3018
+	public function in_entity_map()
3019
+	{
3020
+		// well, if we looked, did we find it in the entity map?
3021
+		return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3022
+	}
3023
+
3024
+
3025
+	/**
3026
+	 * refresh_from_db
3027
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3028
+	 *
3029
+	 * @throws ReflectionException
3030
+	 * @throws InvalidArgumentException
3031
+	 * @throws InvalidInterfaceException
3032
+	 * @throws InvalidDataTypeException
3033
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3034
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3035
+	 */
3036
+	public function refresh_from_db()
3037
+	{
3038
+		if ($this->ID() && $this->in_entity_map()) {
3039
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
3040
+		} else {
3041
+			//if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3042
+			//if it has an ID but it's not in the map, and you're asking me to refresh it
3043
+			//that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3044
+			//absolutely nothing in it for this ID
3045
+			if (WP_DEBUG) {
3046
+				throw new EE_Error(
3047
+					sprintf(
3048
+						esc_html__('Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3049
+							'event_espresso'),
3050
+						$this->ID(),
3051
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3052
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3053
+					)
3054
+				);
3055
+			}
3056
+		}
3057
+	}
3058
+
3059
+
3060
+	/**
3061
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3062
+	 * (probably a bad assumption they have made, oh well)
3063
+	 *
3064
+	 * @return string
3065
+	 */
3066
+	public function __toString()
3067
+	{
3068
+		try {
3069
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3070
+		} catch (Exception $e) {
3071
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3072
+			return '';
3073
+		}
3074
+	}
3075
+
3076
+
3077
+	/**
3078
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3079
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3080
+	 * This means if we have made changes to those related model objects, and want to unserialize
3081
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3082
+	 * Instead, those related model objects should be directly serialized and stored.
3083
+	 * Eg, the following won't work:
3084
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3085
+	 * $att = $reg->attendee();
3086
+	 * $att->set( 'ATT_fname', 'Dirk' );
3087
+	 * update_option( 'my_option', serialize( $reg ) );
3088
+	 * //END REQUEST
3089
+	 * //START NEXT REQUEST
3090
+	 * $reg = get_option( 'my_option' );
3091
+	 * $reg->attendee()->save();
3092
+	 * And would need to be replace with:
3093
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3094
+	 * $att = $reg->attendee();
3095
+	 * $att->set( 'ATT_fname', 'Dirk' );
3096
+	 * update_option( 'my_option', serialize( $reg ) );
3097
+	 * //END REQUEST
3098
+	 * //START NEXT REQUEST
3099
+	 * $att = get_option( 'my_option' );
3100
+	 * $att->save();
3101
+	 *
3102
+	 * @return array
3103
+	 * @throws ReflectionException
3104
+	 * @throws InvalidArgumentException
3105
+	 * @throws InvalidInterfaceException
3106
+	 * @throws InvalidDataTypeException
3107
+	 * @throws EE_Error
3108
+	 */
3109
+	public function __sleep()
3110
+	{
3111
+		$model = $this->get_model();
3112
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3113
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3114
+				$classname = 'EE_' . $model->get_this_model_name();
3115
+				if (
3116
+					$this->get_one_from_cache($relation_name) instanceof $classname
3117
+					&& $this->get_one_from_cache($relation_name)->ID()
3118
+				) {
3119
+					$this->clear_cache(
3120
+						$relation_name,
3121
+						$this->get_one_from_cache($relation_name)->ID()
3122
+					);
3123
+				}
3124
+			}
3125
+		}
3126
+		$this->_props_n_values_provided_in_constructor = array();
3127
+		$properties_to_serialize                       = get_object_vars($this);
3128
+		//don't serialize the model. It's big and that risks recursion
3129
+		unset($properties_to_serialize['_model']);
3130
+		return array_keys($properties_to_serialize);
3131
+	}
3132
+
3133
+
3134
+	/**
3135
+	 * restore _props_n_values_provided_in_constructor
3136
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3137
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3138
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3139
+	 */
3140
+	public function __wakeup()
3141
+	{
3142
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3143
+	}
3144
+
3145
+
3146
+	/**
3147
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3148
+	 * distinct with the clone host instance are also cloned.
3149
+	 */
3150
+	public function __clone()
3151
+	{
3152
+		//handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3153
+		foreach ($this->_fields as $field => $value) {
3154
+			if ($value instanceof DateTime) {
3155
+				$this->_fields[$field] = clone $value;
3156
+			}
3157
+		}
3158
+	}
3159 3159
 }
3160 3160
 
3161 3161
 
Please login to merge, or discard this patch.
Spacing   +115 added lines, -115 removed lines patch added patch discarded remove patch
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
         $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
149 149
         // verify client code has not passed any invalid field names
150 150
         foreach ($fieldValues as $field_name => $field_value) {
151
-            if (! isset($model_fields[ $field_name ])) {
151
+            if ( ! isset($model_fields[$field_name])) {
152 152
                 throw new EE_Error(
153 153
                     sprintf(
154 154
                         esc_html__(
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
             }
164 164
         }
165 165
         $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
166
-        if (! empty($date_formats) && is_array($date_formats)) {
166
+        if ( ! empty($date_formats) && is_array($date_formats)) {
167 167
             list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
168 168
         } else {
169 169
             //set default formats for date and time
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
             foreach ($model_fields as $fieldName => $field) {
177 177
                 $this->set_from_db(
178 178
                     $fieldName,
179
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
179
+                    isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null
180 180
                 );
181 181
             }
182 182
         } else {
@@ -185,22 +185,22 @@  discard block
 block discarded – undo
185 185
             foreach ($model_fields as $fieldName => $field) {
186 186
                 $this->set(
187 187
                     $fieldName,
188
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null, true
188
+                    isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null, true
189 189
                 );
190 190
             }
191 191
         }
192 192
         //remember what values were passed to this constructor
193 193
         $this->_props_n_values_provided_in_constructor = $fieldValues;
194 194
         //remember in entity mapper
195
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
+        if ( ! $bydb && $model->has_primary_key_field() && $this->ID()) {
196 196
             $model->add_to_entity_map($this);
197 197
         }
198 198
         //setup all the relations
199 199
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
200 200
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
-                $this->_model_relations[ $relation_name ] = null;
201
+                $this->_model_relations[$relation_name] = null;
202 202
             } else {
203
-                $this->_model_relations[ $relation_name ] = array();
203
+                $this->_model_relations[$relation_name] = array();
204 204
             }
205 205
         }
206 206
         /**
@@ -251,10 +251,10 @@  discard block
 block discarded – undo
251 251
      */
252 252
     public function get_original($field_name)
253 253
     {
254
-        if (isset($this->_props_n_values_provided_in_constructor[ $field_name ])
254
+        if (isset($this->_props_n_values_provided_in_constructor[$field_name])
255 255
             && $field_settings = $this->get_model()->field_settings_for($field_name)
256 256
         ) {
257
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
257
+            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
258 258
         }
259 259
         return null;
260 260
     }
@@ -291,7 +291,7 @@  discard block
 block discarded – undo
291 291
         // then don't do anything
292 292
         if (
293 293
             ! $use_default
294
-            && $this->_fields[ $field_name ] === $field_value
294
+            && $this->_fields[$field_name] === $field_value
295 295
             && $this->ID()
296 296
         ) {
297 297
             return;
@@ -309,7 +309,7 @@  discard block
 block discarded – undo
309 309
             $holder_of_value = $field_obj->prepare_for_set($field_value);
310 310
             //should the value be null?
311 311
             if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
312
+                $this->_fields[$field_name] = $field_obj->get_default_value();
313 313
                 /**
314 314
                  * To save having to refactor all the models, if a default value is used for a
315 315
                  * EE_Datetime_Field, and that value is not null nor is it a DateTime
@@ -320,15 +320,15 @@  discard block
 block discarded – undo
320 320
                  */
321 321
                 if (
322 322
                     $field_obj instanceof EE_Datetime_Field
323
-                    && $this->_fields[ $field_name ] !== null
324
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
323
+                    && $this->_fields[$field_name] !== null
324
+                    && ! $this->_fields[$field_name] instanceof DateTime
325 325
                 ) {
326
-                    empty($this->_fields[ $field_name ])
326
+                    empty($this->_fields[$field_name])
327 327
                         ? $this->set($field_name, time())
328
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
328
+                        : $this->set($field_name, $this->_fields[$field_name]);
329 329
                 }
330 330
             } else {
331
-                $this->_fields[ $field_name ] = $holder_of_value;
331
+                $this->_fields[$field_name] = $holder_of_value;
332 332
             }
333 333
             //if we're not in the constructor...
334 334
             //now check if what we set was a primary key
@@ -345,7 +345,7 @@  discard block
 block discarded – undo
345 345
                 $fields_on_model = self::_get_model(get_class($this))->field_settings();
346 346
                 $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347 347
                 foreach ($fields_on_model as $field_obj) {
348
-                    if (! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
348
+                    if ( ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
349 349
                         && $field_obj->get_name() !== $field_name
350 350
                     ) {
351 351
                         $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
@@ -390,8 +390,8 @@  discard block
 block discarded – undo
390 390
      */
391 391
     public function getCustomSelect($alias)
392 392
     {
393
-        return isset($this->custom_selection_results[ $alias ])
394
-            ? $this->custom_selection_results[ $alias ]
393
+        return isset($this->custom_selection_results[$alias])
394
+            ? $this->custom_selection_results[$alias]
395 395
             : null;
396 396
     }
397 397
 
@@ -478,7 +478,7 @@  discard block
 block discarded – undo
478 478
         foreach ($model_fields as $field_name => $field_obj) {
479 479
             if ($field_obj instanceof EE_Datetime_Field) {
480 480
                 $field_obj->set_timezone($this->_timezone);
481
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
481
+                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
482 482
                     EEH_DTT_Helper::setTimezone($this->_fields[$field_name], new DateTimeZone($this->_timezone));
483 483
                 }
484 484
             }
@@ -537,7 +537,7 @@  discard block
 block discarded – undo
537 537
      */
538 538
     public function get_format($full = true)
539 539
     {
540
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
540
+        return $full ? $this->_dt_frmt.' '.$this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
541 541
     }
542 542
 
543 543
 
@@ -563,11 +563,11 @@  discard block
 block discarded – undo
563 563
     public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
564 564
     {
565 565
         // its entirely possible that there IS no related object yet in which case there is nothing to cache.
566
-        if (! $object_to_cache instanceof EE_Base_Class) {
566
+        if ( ! $object_to_cache instanceof EE_Base_Class) {
567 567
             return false;
568 568
         }
569 569
         // also get "how" the object is related, or throw an error
570
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
570
+        if ( ! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
571 571
             throw new EE_Error(
572 572
                 sprintf(
573 573
                     esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
@@ -581,38 +581,38 @@  discard block
 block discarded – undo
581 581
             // if it's a "belongs to" relationship, then there's only one related model object
582 582
             // eg, if this is a registration, there's only 1 attendee for it
583 583
             // so for these model objects just set it to be cached
584
-            $this->_model_relations[ $relationName ] = $object_to_cache;
584
+            $this->_model_relations[$relationName] = $object_to_cache;
585 585
             $return                                  = true;
586 586
         } else {
587 587
             // otherwise, this is the "many" side of a one to many relationship,
588 588
             // so we'll add the object to the array of related objects for that type.
589 589
             // eg: if this is an event, there are many registrations for that event,
590 590
             // so we cache the registrations in an array
591
-            if (! is_array($this->_model_relations[ $relationName ])) {
591
+            if ( ! is_array($this->_model_relations[$relationName])) {
592 592
                 // if for some reason, the cached item is a model object,
593 593
                 // then stick that in the array, otherwise start with an empty array
594
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
594
+                $this->_model_relations[$relationName] = $this->_model_relations[$relationName]
595 595
                                                            instanceof
596 596
                                                            EE_Base_Class
597
-                    ? array($this->_model_relations[ $relationName ]) : array();
597
+                    ? array($this->_model_relations[$relationName]) : array();
598 598
             }
599 599
             // first check for a cache_id which is normally empty
600
-            if (! empty($cache_id)) {
600
+            if ( ! empty($cache_id)) {
601 601
                 // if the cache_id exists, then it means we are purposely trying to cache this
602 602
                 // with a known key that can then be used to retrieve the object later on
603
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
603
+                $this->_model_relations[$relationName][$cache_id] = $object_to_cache;
604 604
                 $return                                               = $cache_id;
605 605
             } elseif ($object_to_cache->ID()) {
606 606
                 // OR the cached object originally came from the db, so let's just use it's PK for an ID
607
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
607
+                $this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
608 608
                 $return                                                            = $object_to_cache->ID();
609 609
             } else {
610 610
                 // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
611
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
611
+                $this->_model_relations[$relationName][] = $object_to_cache;
612 612
                 // move the internal pointer to the end of the array
613
-                end($this->_model_relations[ $relationName ]);
613
+                end($this->_model_relations[$relationName]);
614 614
                 // and grab the key so that we can return it
615
-                $return = key($this->_model_relations[ $relationName ]);
615
+                $return = key($this->_model_relations[$relationName]);
616 616
             }
617 617
         }
618 618
         return $return;
@@ -638,7 +638,7 @@  discard block
 block discarded – undo
638 638
         //first make sure this property exists
639 639
         $this->get_model()->field_settings_for($fieldname);
640 640
         $cache_type                                            = empty($cache_type) ? 'standard' : $cache_type;
641
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
641
+        $this->_cached_properties[$fieldname][$cache_type] = $value;
642 642
     }
643 643
 
644 644
 
@@ -667,9 +667,9 @@  discard block
 block discarded – undo
667 667
         $model = $this->get_model();
668 668
         $model->field_settings_for($fieldname);
669 669
         $cache_type = $pretty ? 'pretty' : 'standard';
670
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
671
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
672
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
670
+        $cache_type .= ! empty($extra_cache_ref) ? '_'.$extra_cache_ref : '';
671
+        if (isset($this->_cached_properties[$fieldname][$cache_type])) {
672
+            return $this->_cached_properties[$fieldname][$cache_type];
673 673
         }
674 674
         $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
675 675
         $this->_set_cached_property($fieldname, $value, $cache_type);
@@ -697,12 +697,12 @@  discard block
 block discarded – undo
697 697
         if ($field_obj instanceof EE_Datetime_Field) {
698 698
             $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
699 699
         }
700
-        if (! isset($this->_fields[ $fieldname ])) {
701
-            $this->_fields[ $fieldname ] = null;
700
+        if ( ! isset($this->_fields[$fieldname])) {
701
+            $this->_fields[$fieldname] = null;
702 702
         }
703 703
         $value = $pretty
704
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
705
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
704
+            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$fieldname], $extra_cache_ref)
705
+            : $field_obj->prepare_for_get($this->_fields[$fieldname]);
706 706
         return $value;
707 707
     }
708 708
 
@@ -760,8 +760,8 @@  discard block
 block discarded – undo
760 760
      */
761 761
     protected function _clear_cached_property($property_name)
762 762
     {
763
-        if (isset($this->_cached_properties[ $property_name ])) {
764
-            unset($this->_cached_properties[ $property_name ]);
763
+        if (isset($this->_cached_properties[$property_name])) {
764
+            unset($this->_cached_properties[$property_name]);
765 765
         }
766 766
     }
767 767
 
@@ -813,7 +813,7 @@  discard block
 block discarded – undo
813 813
     {
814 814
         $relationship_to_model = $this->get_model()->related_settings_for($relationName);
815 815
         $index_in_cache        = '';
816
-        if (! $relationship_to_model) {
816
+        if ( ! $relationship_to_model) {
817 817
             throw new EE_Error(
818 818
                 sprintf(
819 819
                     esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
@@ -824,21 +824,21 @@  discard block
 block discarded – undo
824 824
         }
825 825
         if ($clear_all) {
826 826
             $obj_removed                             = true;
827
-            $this->_model_relations[ $relationName ] = null;
827
+            $this->_model_relations[$relationName] = null;
828 828
         } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
829
-            $obj_removed                             = $this->_model_relations[ $relationName ];
830
-            $this->_model_relations[ $relationName ] = null;
829
+            $obj_removed                             = $this->_model_relations[$relationName];
830
+            $this->_model_relations[$relationName] = null;
831 831
         } else {
832 832
             if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
833 833
                 && $object_to_remove_or_index_into_array->ID()
834 834
             ) {
835 835
                 $index_in_cache = $object_to_remove_or_index_into_array->ID();
836
-                if (is_array($this->_model_relations[ $relationName ])
837
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
836
+                if (is_array($this->_model_relations[$relationName])
837
+                    && ! isset($this->_model_relations[$relationName][$index_in_cache])
838 838
                 ) {
839 839
                     $index_found_at = null;
840 840
                     //find this object in the array even though it has a different key
841
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
841
+                    foreach ($this->_model_relations[$relationName] as $index => $obj) {
842 842
                         /** @noinspection TypeUnsafeComparisonInspection */
843 843
                         if (
844 844
                             $obj instanceof EE_Base_Class
@@ -872,9 +872,9 @@  discard block
 block discarded – undo
872 872
             }
873 873
             //supposedly we've found it. But it could just be that the client code
874 874
             //provided a bad index/object
875
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
876
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
877
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
875
+            if (isset($this->_model_relations[$relationName][$index_in_cache])) {
876
+                $obj_removed = $this->_model_relations[$relationName][$index_in_cache];
877
+                unset($this->_model_relations[$relationName][$index_in_cache]);
878 878
             } else {
879 879
                 //that thing was never cached anyways.
880 880
                 $obj_removed = null;
@@ -905,7 +905,7 @@  discard block
 block discarded – undo
905 905
         $current_cache_id = ''
906 906
     ) {
907 907
         // verify that incoming object is of the correct type
908
-        $obj_class = 'EE_' . $relationName;
908
+        $obj_class = 'EE_'.$relationName;
909 909
         if ($newly_saved_object instanceof $obj_class) {
910 910
             /* @type EE_Base_Class $newly_saved_object */
911 911
             // now get the type of relation
@@ -913,17 +913,17 @@  discard block
 block discarded – undo
913 913
             // if this is a 1:1 relationship
914 914
             if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
915 915
                 // then just replace the cached object with the newly saved object
916
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
916
+                $this->_model_relations[$relationName] = $newly_saved_object;
917 917
                 return true;
918 918
                 // or if it's some kind of sordid feral polyamorous relationship...
919 919
             }
920
-            if (is_array($this->_model_relations[ $relationName ])
921
-                      && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
920
+            if (is_array($this->_model_relations[$relationName])
921
+                      && isset($this->_model_relations[$relationName][$current_cache_id])
922 922
             ) {
923 923
                 // then remove the current cached item
924
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
924
+                unset($this->_model_relations[$relationName][$current_cache_id]);
925 925
                 // and cache the newly saved object using it's new ID
926
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
926
+                $this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
927 927
                 return true;
928 928
             }
929 929
         }
@@ -940,8 +940,8 @@  discard block
 block discarded – undo
940 940
      */
941 941
     public function get_one_from_cache($relationName)
942 942
     {
943
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
944
-            ? $this->_model_relations[ $relationName ]
943
+        $cached_array_or_object = isset($this->_model_relations[$relationName])
944
+            ? $this->_model_relations[$relationName]
945 945
             : null;
946 946
         if (is_array($cached_array_or_object)) {
947 947
             return array_shift($cached_array_or_object);
@@ -964,7 +964,7 @@  discard block
 block discarded – undo
964 964
      */
965 965
     public function get_all_from_cache($relationName)
966 966
     {
967
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
967
+        $objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : array();
968 968
         // if the result is not an array, but exists, make it an array
969 969
         $objects = is_array($objects) ? $objects : array($objects);
970 970
         //bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
@@ -1148,7 +1148,7 @@  discard block
 block discarded – undo
1148 1148
             } else {
1149 1149
                 $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1150 1150
             }
1151
-            $this->_fields[ $field_name ] = $field_value;
1151
+            $this->_fields[$field_name] = $field_value;
1152 1152
             $this->_clear_cached_property($field_name);
1153 1153
         }
1154 1154
     }
@@ -1188,9 +1188,9 @@  discard block
 block discarded – undo
1188 1188
     public function get_raw($field_name)
1189 1189
     {
1190 1190
         $field_settings = $this->get_model()->field_settings_for($field_name);
1191
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1192
-            ? $this->_fields[ $field_name ]->format('U')
1193
-            : $this->_fields[ $field_name ];
1191
+        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
1192
+            ? $this->_fields[$field_name]->format('U')
1193
+            : $this->_fields[$field_name];
1194 1194
     }
1195 1195
 
1196 1196
 
@@ -1212,7 +1212,7 @@  discard block
 block discarded – undo
1212 1212
     public function get_DateTime_object($field_name)
1213 1213
     {
1214 1214
         $field_settings = $this->get_model()->field_settings_for($field_name);
1215
-        if (! $field_settings instanceof EE_Datetime_Field) {
1215
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1216 1216
             EE_Error::add_error(
1217 1217
                 sprintf(
1218 1218
                     esc_html__(
@@ -1470,7 +1470,7 @@  discard block
 block discarded – undo
1470 1470
      */
1471 1471
     public function get_i18n_datetime($field_name, $format = '')
1472 1472
     {
1473
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1473
+        $format = empty($format) ? $this->_dt_frmt.' '.$this->_tm_frmt : $format;
1474 1474
         return date_i18n(
1475 1475
             $format,
1476 1476
             EEH_DTT_Helper::get_timestamp_with_offset(
@@ -1582,19 +1582,19 @@  discard block
 block discarded – undo
1582 1582
         $field->set_time_format($this->_tm_frmt);
1583 1583
         switch ($what) {
1584 1584
             case 'T' :
1585
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1585
+                $this->_fields[$fieldname] = $field->prepare_for_set_with_new_time(
1586 1586
                     $datetime_value,
1587
-                    $this->_fields[ $fieldname ]
1587
+                    $this->_fields[$fieldname]
1588 1588
                 );
1589 1589
                 break;
1590 1590
             case 'D' :
1591
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1591
+                $this->_fields[$fieldname] = $field->prepare_for_set_with_new_date(
1592 1592
                     $datetime_value,
1593
-                    $this->_fields[ $fieldname ]
1593
+                    $this->_fields[$fieldname]
1594 1594
                 );
1595 1595
                 break;
1596 1596
             case 'B' :
1597
-                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1597
+                $this->_fields[$fieldname] = $field->prepare_for_set($datetime_value);
1598 1598
                 break;
1599 1599
         }
1600 1600
         $this->_clear_cached_property($fieldname);
@@ -1636,7 +1636,7 @@  discard block
 block discarded – undo
1636 1636
         $this->set_timezone($timezone);
1637 1637
         $fn   = (array) $field_name;
1638 1638
         $args = array_merge($fn, (array) $args);
1639
-        if (! method_exists($this, $callback)) {
1639
+        if ( ! method_exists($this, $callback)) {
1640 1640
             throw new EE_Error(
1641 1641
                 sprintf(
1642 1642
                     esc_html__(
@@ -1648,7 +1648,7 @@  discard block
 block discarded – undo
1648 1648
             );
1649 1649
         }
1650 1650
         $args   = (array) $args;
1651
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1651
+        $return = $prepend.call_user_func_array(array($this, $callback), $args).$append;
1652 1652
         $this->set_timezone($original_timezone);
1653 1653
         return $return;
1654 1654
     }
@@ -1763,8 +1763,8 @@  discard block
 block discarded – undo
1763 1763
     {
1764 1764
         $model = $this->get_model();
1765 1765
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1766
-            if (! empty($this->_model_relations[ $relation_name ])) {
1767
-                $related_objects = $this->_model_relations[ $relation_name ];
1766
+            if ( ! empty($this->_model_relations[$relation_name])) {
1767
+                $related_objects = $this->_model_relations[$relation_name];
1768 1768
                 if ($relation_obj instanceof EE_Belongs_To_Relation) {
1769 1769
                     //this relation only stores a single model object, not an array
1770 1770
                     //but let's make it consistent
@@ -1821,7 +1821,7 @@  discard block
 block discarded – undo
1821 1821
             $this->set($column, $value);
1822 1822
         }
1823 1823
         // no changes ? then don't do anything
1824
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1824
+        if ( ! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1825 1825
             return 0;
1826 1826
         }
1827 1827
         /**
@@ -1831,7 +1831,7 @@  discard block
 block discarded – undo
1831 1831
          * @param EE_Base_Class $model_object the model object about to be saved.
1832 1832
          */
1833 1833
         do_action('AHEE__EE_Base_Class__save__begin', $this);
1834
-        if (! $this->allow_persist()) {
1834
+        if ( ! $this->allow_persist()) {
1835 1835
             return 0;
1836 1836
         }
1837 1837
         // now get current attribute values
@@ -1846,10 +1846,10 @@  discard block
 block discarded – undo
1846 1846
         if ($model->has_primary_key_field()) {
1847 1847
             if ($model->get_primary_key_field()->is_auto_increment()) {
1848 1848
                 //ok check if it's set, if so: update; if not, insert
1849
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1849
+                if ( ! empty($save_cols_n_values[$model->primary_key_name()])) {
1850 1850
                     $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1851 1851
                 } else {
1852
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1852
+                    unset($save_cols_n_values[$model->primary_key_name()]);
1853 1853
                     $results = $model->insert($save_cols_n_values);
1854 1854
                     if ($results) {
1855 1855
                         //if successful, set the primary key
@@ -1859,7 +1859,7 @@  discard block
 block discarded – undo
1859 1859
                         //will get added to the mapper before we can add this one!
1860 1860
                         //but if we just avoid using the SET method, all that headache can be avoided
1861 1861
                         $pk_field_name                   = $model->primary_key_name();
1862
-                        $this->_fields[ $pk_field_name ] = $results;
1862
+                        $this->_fields[$pk_field_name] = $results;
1863 1863
                         $this->_clear_cached_property($pk_field_name);
1864 1864
                         $model->add_to_entity_map($this);
1865 1865
                         $this->_update_cached_related_model_objs_fks();
@@ -1876,8 +1876,8 @@  discard block
 block discarded – undo
1876 1876
                                     'event_espresso'
1877 1877
                                 ),
1878 1878
                                 get_class($this),
1879
-                                get_class($model) . '::instance()->add_to_entity_map()',
1880
-                                get_class($model) . '::instance()->get_one_by_ID()',
1879
+                                get_class($model).'::instance()->add_to_entity_map()',
1880
+                                get_class($model).'::instance()->get_one_by_ID()',
1881 1881
                                 '<br />'
1882 1882
                             )
1883 1883
                         );
@@ -1977,27 +1977,27 @@  discard block
 block discarded – undo
1977 1977
     public function save_new_cached_related_model_objs()
1978 1978
     {
1979 1979
         //make sure this has been saved
1980
-        if (! $this->ID()) {
1980
+        if ( ! $this->ID()) {
1981 1981
             $id = $this->save();
1982 1982
         } else {
1983 1983
             $id = $this->ID();
1984 1984
         }
1985 1985
         //now save all the NEW cached model objects  (ie they don't exist in the DB)
1986 1986
         foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1987
-            if ($this->_model_relations[ $relationName ]) {
1987
+            if ($this->_model_relations[$relationName]) {
1988 1988
                 //is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1989 1989
                 //or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1990 1990
                 /* @var $related_model_obj EE_Base_Class */
1991 1991
                 if ($relationObj instanceof EE_Belongs_To_Relation) {
1992 1992
                     //add a relation to that relation type (which saves the appropriate thing in the process)
1993 1993
                     //but ONLY if it DOES NOT exist in the DB
1994
-                    $related_model_obj = $this->_model_relations[ $relationName ];
1994
+                    $related_model_obj = $this->_model_relations[$relationName];
1995 1995
                     //					if( ! $related_model_obj->ID()){
1996 1996
                     $this->_add_relation_to($related_model_obj, $relationName);
1997 1997
                     $related_model_obj->save_new_cached_related_model_objs();
1998 1998
                     //					}
1999 1999
                 } else {
2000
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2000
+                    foreach ($this->_model_relations[$relationName] as $related_model_obj) {
2001 2001
                         //add a relation to that relation type (which saves the appropriate thing in the process)
2002 2002
                         //but ONLY if it DOES NOT exist in the DB
2003 2003
                         //						if( ! $related_model_obj->ID()){
@@ -2024,7 +2024,7 @@  discard block
 block discarded – undo
2024 2024
      */
2025 2025
     public function get_model()
2026 2026
     {
2027
-        if (! $this->_model) {
2027
+        if ( ! $this->_model) {
2028 2028
             $modelName    = self::_get_model_classname(get_class($this));
2029 2029
             $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2030 2030
         } else {
@@ -2050,9 +2050,9 @@  discard block
 block discarded – undo
2050 2050
         $primary_id_ref = self::_get_primary_key_name($classname);
2051 2051
         if (
2052 2052
             array_key_exists($primary_id_ref, $props_n_values)
2053
-            && ! empty($props_n_values[ $primary_id_ref ])
2053
+            && ! empty($props_n_values[$primary_id_ref])
2054 2054
         ) {
2055
-            $id = $props_n_values[ $primary_id_ref ];
2055
+            $id = $props_n_values[$primary_id_ref];
2056 2056
             return self::_get_model($classname)->get_from_entity_map($id);
2057 2057
         }
2058 2058
         return false;
@@ -2086,10 +2086,10 @@  discard block
 block discarded – undo
2086 2086
         if ($model->has_primary_key_field()) {
2087 2087
             $primary_id_ref = self::_get_primary_key_name($classname);
2088 2088
             if (array_key_exists($primary_id_ref, $props_n_values)
2089
-                && ! empty($props_n_values[ $primary_id_ref ])
2089
+                && ! empty($props_n_values[$primary_id_ref])
2090 2090
             ) {
2091 2091
                 $existing = $model->get_one_by_ID(
2092
-                    $props_n_values[ $primary_id_ref ]
2092
+                    $props_n_values[$primary_id_ref]
2093 2093
                 );
2094 2094
             }
2095 2095
         } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
@@ -2101,7 +2101,7 @@  discard block
 block discarded – undo
2101 2101
         }
2102 2102
         if ($existing) {
2103 2103
             //set date formats if present before setting values
2104
-            if (! empty($date_formats) && is_array($date_formats)) {
2104
+            if ( ! empty($date_formats) && is_array($date_formats)) {
2105 2105
                 $existing->set_date_format($date_formats[0]);
2106 2106
                 $existing->set_time_format($date_formats[1]);
2107 2107
             } else {
@@ -2134,7 +2134,7 @@  discard block
 block discarded – undo
2134 2134
     protected static function _get_model($classname, $timezone = null)
2135 2135
     {
2136 2136
         //find model for this class
2137
-        if (! $classname) {
2137
+        if ( ! $classname) {
2138 2138
             throw new EE_Error(
2139 2139
                 sprintf(
2140 2140
                     esc_html__(
@@ -2183,7 +2183,7 @@  discard block
 block discarded – undo
2183 2183
         if (strpos($model_name, 'EE_') === 0) {
2184 2184
             $model_classname = str_replace('EE_', 'EEM_', $model_name);
2185 2185
         } else {
2186
-            $model_classname = 'EEM_' . $model_name;
2186
+            $model_classname = 'EEM_'.$model_name;
2187 2187
         }
2188 2188
         return $model_classname;
2189 2189
     }
@@ -2202,7 +2202,7 @@  discard block
 block discarded – undo
2202 2202
      */
2203 2203
     protected static function _get_primary_key_name($classname = null)
2204 2204
     {
2205
-        if (! $classname) {
2205
+        if ( ! $classname) {
2206 2206
             throw new EE_Error(
2207 2207
                 sprintf(
2208 2208
                     esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
@@ -2232,7 +2232,7 @@  discard block
 block discarded – undo
2232 2232
         $model = $this->get_model();
2233 2233
         //now that we know the name of the variable, use a variable variable to get its value and return its
2234 2234
         if ($model->has_primary_key_field()) {
2235
-            return $this->_fields[ $model->primary_key_name() ];
2235
+            return $this->_fields[$model->primary_key_name()];
2236 2236
         }
2237 2237
         return $model->get_index_primary_key_string($this->_fields);
2238 2238
     }
@@ -2285,7 +2285,7 @@  discard block
 block discarded – undo
2285 2285
             }
2286 2286
         } else {
2287 2287
             //this thing doesn't exist in the DB,  so just cache it
2288
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2288
+            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2289 2289
                 throw new EE_Error(
2290 2290
                     sprintf(
2291 2291
                         esc_html__(
@@ -2450,7 +2450,7 @@  discard block
 block discarded – undo
2450 2450
             } else {
2451 2451
                 //did we already cache the result of this query?
2452 2452
                 $cached_results = $this->get_all_from_cache($relationName);
2453
-                if (! $cached_results) {
2453
+                if ( ! $cached_results) {
2454 2454
                     $related_model_objects = $this->get_model()->get_all_related(
2455 2455
                         $this,
2456 2456
                         $relationName,
@@ -2560,7 +2560,7 @@  discard block
 block discarded – undo
2560 2560
             } else {
2561 2561
                 //first, check if we've already cached the result of this query
2562 2562
                 $cached_result = $this->get_one_from_cache($relationName);
2563
-                if (! $cached_result) {
2563
+                if ( ! $cached_result) {
2564 2564
                     $related_model_object = $model->get_first_related(
2565 2565
                         $this,
2566 2566
                         $relationName,
@@ -2584,7 +2584,7 @@  discard block
 block discarded – undo
2584 2584
             }
2585 2585
             // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2586 2586
             // just get what's cached on this object
2587
-            if (! $related_model_object) {
2587
+            if ( ! $related_model_object) {
2588 2588
                 $related_model_object = $this->get_one_from_cache($relationName);
2589 2589
             }
2590 2590
         }
@@ -2666,7 +2666,7 @@  discard block
 block discarded – undo
2666 2666
      */
2667 2667
     public function is_set($field_name)
2668 2668
     {
2669
-        return isset($this->_fields[ $field_name ]);
2669
+        return isset($this->_fields[$field_name]);
2670 2670
     }
2671 2671
 
2672 2672
 
@@ -2682,7 +2682,7 @@  discard block
 block discarded – undo
2682 2682
     {
2683 2683
         foreach ((array) $properties as $property_name) {
2684 2684
             //first make sure this property exists
2685
-            if (! $this->_fields[ $property_name ]) {
2685
+            if ( ! $this->_fields[$property_name]) {
2686 2686
                 throw new EE_Error(
2687 2687
                     sprintf(
2688 2688
                         esc_html__(
@@ -2714,7 +2714,7 @@  discard block
 block discarded – undo
2714 2714
         $properties = array();
2715 2715
         //remove prepended underscore
2716 2716
         foreach ($fields as $field_name => $settings) {
2717
-            $properties[ $field_name ] = $this->get($field_name);
2717
+            $properties[$field_name] = $this->get($field_name);
2718 2718
         }
2719 2719
         return $properties;
2720 2720
     }
@@ -2751,7 +2751,7 @@  discard block
 block discarded – undo
2751 2751
     {
2752 2752
         $className = get_class($this);
2753 2753
         $tagName   = "FHEE__{$className}__{$methodName}";
2754
-        if (! has_filter($tagName)) {
2754
+        if ( ! has_filter($tagName)) {
2755 2755
             throw new EE_Error(
2756 2756
                 sprintf(
2757 2757
                     esc_html__(
@@ -2796,7 +2796,7 @@  discard block
 block discarded – undo
2796 2796
             $query_params[0]['EXM_value'] = $meta_value;
2797 2797
         }
2798 2798
         $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2799
-        if (! $existing_rows_like_that) {
2799
+        if ( ! $existing_rows_like_that) {
2800 2800
             return $this->add_extra_meta($meta_key, $meta_value);
2801 2801
         }
2802 2802
         foreach ($existing_rows_like_that as $existing_row) {
@@ -2914,7 +2914,7 @@  discard block
 block discarded – undo
2914 2914
                 $values = array();
2915 2915
                 foreach ($results as $result) {
2916 2916
                     if ($result instanceof EE_Extra_Meta) {
2917
-                        $values[ $result->ID() ] = $result->value();
2917
+                        $values[$result->ID()] = $result->value();
2918 2918
                     }
2919 2919
                 }
2920 2920
                 return $values;
@@ -2959,17 +2959,17 @@  discard block
 block discarded – undo
2959 2959
             );
2960 2960
             foreach ($extra_meta_objs as $extra_meta_obj) {
2961 2961
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2962
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2962
+                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2963 2963
                 }
2964 2964
             }
2965 2965
         } else {
2966 2966
             $extra_meta_objs = $this->get_many_related('Extra_Meta');
2967 2967
             foreach ($extra_meta_objs as $extra_meta_obj) {
2968 2968
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2969
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2970
-                        $return_array[ $extra_meta_obj->key() ] = array();
2969
+                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2970
+                        $return_array[$extra_meta_obj->key()] = array();
2971 2971
                     }
2972
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2972
+                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2973 2973
                 }
2974 2974
             }
2975 2975
         }
@@ -3048,8 +3048,8 @@  discard block
 block discarded – undo
3048 3048
                         esc_html__('Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3049 3049
                             'event_espresso'),
3050 3050
                         $this->ID(),
3051
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3052
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3051
+                        get_class($this->get_model()).'::instance()->add_to_entity_map()',
3052
+                        get_class($this->get_model()).'::instance()->refresh_entity_map()'
3053 3053
                     )
3054 3054
                 );
3055 3055
             }
@@ -3111,7 +3111,7 @@  discard block
 block discarded – undo
3111 3111
         $model = $this->get_model();
3112 3112
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3113 3113
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
3114
-                $classname = 'EE_' . $model->get_this_model_name();
3114
+                $classname = 'EE_'.$model->get_this_model_name();
3115 3115
                 if (
3116 3116
                     $this->get_one_from_cache($relation_name) instanceof $classname
3117 3117
                     && $this->get_one_from_cache($relation_name)->ID()
Please login to merge, or discard this patch.