Completed
Branch BUG-10144-query-quotes (e5d611)
by
unknown
37:22 queued 18:36
created

EE_Session::_espresso_session()   F

Complexity

Conditions 19
Paths 1448

Size

Total Lines 87
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 50
nc 1448
nop 0
dl 0
loc 87
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php use EventEspresso\core\exceptions\InvalidSessionDataException;
2
3
if (!defined( 'EVENT_ESPRESSO_VERSION')) {exit('No direct script access allowed');}
4
/**
5
 *
6
 * EE_Session class
7
 *
8
 * @package    Event Espresso
9
 * @subpackage includes/classes
10
 * @author     Brent Christensen
11
 */
12
 class EE_Session {
13
14
	 const session_id_prefix = 'ee_ssn_';
15
	 const hash_check_prefix = 'ee_shc_';
16
17
	 /**
18
	  * instance of the EE_Session object
19
	  * @var EE_Session
20
	  */
21
	 private static $_instance;
22
23
	 /**
24
	  * the session id
25
	  * @var string
26
	  */
27
	 private $_sid;
28
29
	 /**
30
	  * session id salt
31
	  * @var string
32
	  */
33
	 private $_sid_salt;
34
35
	 /**
36
	  * session data
37
	  * @var array
38
	  */
39
	 private $_session_data = array();
40
41
	 /**
42
	  * how long an EE session lasts
43
	  * default session lifespan of 2 hours (for not so instant IPNs)
44
	  * @var int
45
	  */
46
	 private $_lifespan;
47
48
	 /**
49
	  * session expiration time as Unix timestamp in GMT
50
	  * @var int
51
	  */
52
	 private $_expiration;
53
54
	 /**
55
	  * current time as Unix timestamp in GMT
56
	  * @var int
57
	  */
58
	 private $_time;
59
60
	 /**
61
	  * whether to encrypt session data
62
	  * @var bool
63
	  */
64
	 private $_use_encryption = false;
65
66
	 /**
67
	  * EE_Encryption object
68
	  * @var EE_Encryption
69
	  */
70
	 protected $encryption;
71
72
	 /**
73
	  * well... according to the server...
74
	  * @var null
75
	  */
76
	 private $_user_agent;
77
78
	 /**
79
	  * do you really trust the server ?
80
	  * @var null
81
	  */
82
	 private $_ip_address;
83
84
	 /**
85
	  * current WP user_id
86
	  * @var null
87
	  */
88
	 private $_wp_user_id;
89
90
	 /**
91
	  * array for defining default session vars
92
	  * @var array
93
	  */
94
	 private $_default_session_vars = array (
95
		'id' => NULL,
96
		'user_id' => NULL,
97
		'ip_address' => NULL,
98
		'user_agent' => NULL,
99
		'init_access' => NULL,
100
		'last_access' => NULL,
101
		'expiration' => NULL,
102
		'pages_visited' => array()
103
	);
104
105
106
107
	 /**
108
	  * @singleton method used to instantiate class object
109
	  * @param \EE_Encryption $encryption
110
	  * @return EE_Session
111
	  * @throws InvalidSessionDataException
112
	  * @throws \EE_Error
113
	  */
114
	public static function instance( EE_Encryption $encryption = null ) {
115
		// check if class object is instantiated
116
		// session loading is turned ON by default, but prior to the init hook, can be turned back OFF via:
117
		// add_filter( 'FHEE_load_EE_Session', '__return_false' );
118
		if ( ! self::$_instance instanceof EE_Session && apply_filters( 'FHEE_load_EE_Session', true ) ) {
119
			self::$_instance = new self( $encryption );
120
		}
121
		return self::$_instance;
122
	}
123
124
125
126
	 /**
127
	  * protected constructor to prevent direct creation
128
	  *
129
	  * @Constructor
130
	  * @access protected
131
	  * @param \EE_Encryption $encryption
132
	  * @throws \EE_Error
133
	  * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
134
	  */
135
	 protected function __construct( EE_Encryption $encryption = null ) {
136
137
		// 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' );
138
		if ( ! apply_filters( 'FHEE_load_EE_Session', TRUE ) ) {
139
			return;
140
		}
141
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, '' );
142
		if ( ! defined( 'ESPRESSO_SESSION' ) ) {
143
			define( 'ESPRESSO_SESSION', true );
144
		}
145
		// default session lifespan in seconds
146
		$this->_lifespan = apply_filters(
147
			'FHEE__EE_Session__construct___lifespan',
148
			60 * MINUTE_IN_SECONDS
149
		) + 1;
150
		/*
151
		 * do something like the following to adjust the session lifespan:
152
		 * 		public static function session_lifespan() {
153
		 * 			return 15 * MINUTE_IN_SECONDS;
154
		 * 		}
155
		 */
156
		// retrieve session options from db
157
		$session_settings = get_option( 'ee_session_settings' );
158
		if ( $session_settings !== FALSE ) {
159
			// cycle though existing session options
160
			foreach ( $session_settings as $var_name => $session_setting ) {
161
				// set values for class properties
162
				$var_name = '_' . $var_name;
163
				$this->{$var_name} = $session_setting;
164
			}
165
		}
166
		// are we using encryption?
167
		if ( $this->_use_encryption && $encryption instanceof EE_Encryption ) {
168
			// encrypt data via: $this->encryption->encrypt();
169
			$this->encryption = $encryption;
170
		}
171
		// filter hook allows outside functions/classes/plugins to change default empty cart
172
		$extra_default_session_vars = apply_filters( 'FHEE__EE_Session__construct__extra_default_session_vars', array() );
173
		array_merge( $this->_default_session_vars, $extra_default_session_vars );
174
		// apply default session vars
175
		$this->_set_defaults();
176
		// check for existing session and retrieve it from db
177
		if ( ! $this->_espresso_session() ) {
178
			// or just start a new one
179
			$this->_create_espresso_session();
180
		}
181
		// check request for 'clear_session' param
182
		add_action( 'AHEE__EE_Request_Handler__construct__complete', array( $this, 'wp_loaded' ));
183
		// once everything is all said and done,
184
		add_action( 'shutdown', array( $this, 'update' ), 100 );
185
		add_action( 'shutdown', array( $this, 'garbage_collection' ), 999 );
186
		add_filter( 'wp_redirect', array( $this, 'update_on_redirect' ), 100, 1 );
187
	}
188
189
190
191
	 /**
192
	  * @return int
193
	  */
194
	 public function expiration() {
195
		 return $this->_expiration;
196
	 }
197
198
199
200
	 /**
201
	  * @return int
202
	  */
203
	 public function lifespan() {
204
		 return $this->_lifespan;
205
	 }
206
207
208
209
	/**
210
	 * This just sets some defaults for the _session data property
211
	 *
212
	 * @access private
213
	 * @return void
214
	 */
215
	private function _set_defaults() {
216
		// set some defaults
217
		foreach ( $this->_default_session_vars as $key => $default_var ) {
218
			if ( is_array( $default_var )) {
219
				$this->_session_data[ $key ] = array();
220
			} else {
221
				$this->_session_data[ $key ] = '';
222
			}
223
		}
224
	}
225
226
227
228
	/**
229
	 * @retrieve session data
230
	 * @access	public
231
	 * @return	string
232
	 */
233
	public function id() {
234
		return $this->_sid;
235
	}
236
237
238
239
	 /**
240
	  * @param \EE_Cart $cart
241
	  * @return bool
242
	  */
243
	 public function set_cart( EE_Cart $cart ) {
244
		 $this->_session_data['cart'] = $cart;
245
		 return TRUE;
246
	 }
247
248
249
250
	 /**
251
	  * reset_cart
252
	  */
253
	 public function reset_cart() {
254
		 $this->_session_data['cart'] = NULL;
255
	 }
256
257
258
259
	 /**
260
	  * @return \EE_Cart
261
	  */
262
	 public function cart() {
263
		 return isset( $this->_session_data['cart'] ) ? $this->_session_data['cart'] : NULL;
264
	 }
265
266
267
268
	 /**
269
	  * @param \EE_Checkout $checkout
270
	  * @return bool
271
	  */
272
	 public function set_checkout( EE_Checkout $checkout ) {
273
		 $this->_session_data['checkout'] = $checkout;
274
		 return TRUE;
275
	 }
276
277
278
279
	 /**
280
	  * reset_checkout
281
	  */
282
	 public function reset_checkout() {
283
		 $this->_session_data['checkout'] = NULL;
284
	 }
285
286
287
288
	 /**
289
	  * @return \EE_Checkout
290
	  */
291
	 public function checkout() {
292
		 return isset( $this->_session_data['checkout'] ) ? $this->_session_data['checkout'] : NULL;
293
	 }
294
295
296
297
	 /**
298
	  * @param \EE_Transaction $transaction
299
	  * @return bool
300
	  * @throws \EE_Error
301
	  */
302
	 public function set_transaction( EE_Transaction $transaction ) {
303
		 // first remove the session from the transaction before we save the transaction in the session
304
		 $transaction->set_txn_session_data( NULL );
0 ignored issues
show
Documentation introduced by
NULL is of type null, but the function expects a object<EE_Session>|array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
305
		 $this->_session_data['transaction'] = $transaction;
306
		 return TRUE;
307
	 }
308
309
310
311
	 /**
312
	  * reset_transaction
313
	  */
314
	 public function reset_transaction() {
315
		 $this->_session_data['transaction'] = NULL;
316
	 }
317
318
319
320
	 /**
321
	  * @return \EE_Transaction
322
	  */
323
	 public function transaction() {
324
		 return isset( $this->_session_data['transaction'] ) ? $this->_session_data['transaction'] : NULL;
325
	 }
326
327
328
329
	 /**
330
	  * retrieve session data
331
	  * @access    public
332
	  * @param null $key
333
	  * @param bool $reset_cache
334
	  * @return    array
335
	  */
336
	public function get_session_data( $key = NULL, $reset_cache = FALSE ) {
337
		if ( $reset_cache ) {
338
			$this->reset_cart();
339
			$this->reset_checkout();
340
			$this->reset_transaction();
341
		}
342
		 if ( ! empty( $key ))  {
343
			return  isset( $this->_session_data[ $key ] ) ? $this->_session_data[ $key ] : NULL;
344
		}  else  {
345
			return $this->_session_data;
346
		}
347
	}
348
349
350
351
	 /**
352
	  * set session data
353
	  * @access 	public
354
	  * @param 	array $data
355
	  * @return 	TRUE on success, FALSE on fail
356
	  */
357
	public function set_session_data( $data ) {
358
359
		// nothing ??? bad data ??? go home!
360 View Code Duplication
		if ( empty( $data ) || ! is_array( $data )) {
361
			EE_Error::add_error( __( 'No session data or invalid session data was provided.', 'event_espresso' ), __FILE__, __FUNCTION__, __LINE__ );
362
			return FALSE;
363
		}
364
365
		foreach ( $data as $key =>$value ) {
366
			if ( isset( $this->_default_session_vars[ $key ] )) {
367
				EE_Error::add_error( sprintf( __( 'Sorry! %s is a default session datum and can not be reset.', 'event_espresso' ), $key ), __FILE__, __FUNCTION__, __LINE__ );
368
				return FALSE;
369
			} else {
370
				$this->_session_data[ $key ] = $value;
371
			}
372
		}
373
374
		return TRUE;
375
376
	}
377
378
379
380
	 /**
381
	  * @initiate session
382
	  * @access   private
383
	  * @return TRUE on success, FALSE on fail
384
	  * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
385
	  * @throws \EE_Error
386
	  */
387
	private function _espresso_session() {
388
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, '' );
389
		// check that session has started
390
		if ( session_id() === '' ) {
391
			//starts a new session if one doesn't already exist, or re-initiates an existing one
392
			session_start();
393
		}
394
		// get our modified session ID
395
		$this->_sid = $this->_generate_session_id();
396
		// and the visitors IP
397
		$this->_ip_address = $this->_visitor_ip();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_visitor_ip() of type string is incompatible with the declared type null of property $_ip_address.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
398
		// set the "user agent"
399
		$this->_user_agent = ( isset($_SERVER['HTTP_USER_AGENT'])) ? esc_attr( $_SERVER['HTTP_USER_AGENT'] ) : FALSE;
400
		// now let's retrieve what's in the db
401
		// we're using WP's Transient API to store session data using the PHP session ID as the option name
402
		$session_data = get_transient( EE_Session::session_id_prefix . $this->_sid );
403
		if ( $session_data ) {
404
			if ( apply_filters( 'FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG ) ) {
405
				$hash_check = get_transient( EE_Session::hash_check_prefix . $this->_sid );
406
				if ( $hash_check && $hash_check !== md5( $session_data ) ) {
407
					EE_Error::add_error(
408
						sprintf(
409
							__( 'The stored data for session %1$s failed to pass a hash check and therefore appears to be invalid.', 'event_espresso' ),
410
							EE_Session::session_id_prefix . $this->_sid
411
						),
412
						__FILE__, __FUNCTION__, __LINE__
413
					);
414
				}
415
			}
416
			// decode the data ?
417
            $session_data = $this->valid_base_64($session_data) ? base64_decode($session_data) : $session_data;
418
			// un-encrypt the data ?
419
			$session_data = $this->_use_encryption ? $this->encryption->decrypt( $session_data ) : $session_data;
420
			if ( ! is_array( $session_data ) ) {
421
				try {
422
					$session_data = maybe_unserialize( $session_data );
423
				} catch ( Exception $e ) {
424
					$msg = esc_html__(
425
						'An error occurred while attempting to unserialize the session data.',
426
						'event_espresso'
427
					);
428
					$msg .= WP_DEBUG ? '<br>' . $this->find_serialize_error( $session_data ) : '';
429
					throw new InvalidSessionDataException( $msg, 0, $e );
430
				}
431
			}
432
			// just a check to make sure the session array is indeed an array
433
			if ( ! is_array( $session_data ) ) {
434
				// no?!?! then something is wrong
435
				$msg = esc_html__(
436
					'The session data is missing, invalid, or corrupted.',
437
					'event_espresso'
438
				);
439
				$msg .= WP_DEBUG ? '<br>' . $this->find_serialize_error( $session_data ) : '';
440
				throw new InvalidSessionDataException( $msg );
441
			}
442
			// get the current time in UTC
443
			$this->_time = isset( $this->_time ) ? $this->_time : time();
444
			// and reset the session expiration
445
			$this->_expiration = isset( $session_data['expiration'] )
446
				? $session_data['expiration']
447
				: $this->_time + $this->_lifespan;
448
449
		} else {
450
			// set initial site access time and the session expiration
451
			$this->_set_init_access_and_expiration();
452
			// set referer
453
			$this->_session_data[ 'pages_visited' ][ $this->_session_data['init_access'] ] = isset( $_SERVER['HTTP_REFERER'] )
454
				? esc_attr( $_SERVER['HTTP_REFERER'] )
455
				: '';
456
			// no previous session = go back and create one (on top of the data above)
457
			return FALSE;
458
		}
459
		// now the user agent
460
		if ( $session_data['user_agent'] !== $this->_user_agent ) {
461
			return FALSE;
462
		}
463
		// wait a minute... how old are you?
464
		if ( $this->_time > $this->_expiration ) {
465
			// yer too old fer me!
466
			// wipe out everything that isn't a default session datum
467
			$this->clear_session( __CLASS__, __FUNCTION__ );
468
		}
469
		// make event espresso session data available to plugin
470
		$this->_session_data = array_merge( $this->_session_data, $session_data );
471
		return TRUE;
472
473
	}
474
475
476
477
	 /**
478
	  * _generate_session_id
479
	  * Retrieves the PHP session id either directly from the PHP session,
480
	  * or from the $_REQUEST array if it was passed in from an AJAX request.
481
	  * The session id is then salted and hashed (mmm sounds tasty)
482
	  * so that it can be safely used as a $_REQUEST param
483
	  *
484
	  * @return string
485
	  */
486
	protected function _generate_session_id() {
487
		// check if the SID was passed explicitly, otherwise get from session, then add salt and hash it to reduce length
488
		if ( isset( $_REQUEST[ 'EESID' ] ) ) {
489
			$session_id = sanitize_text_field( $_REQUEST[ 'EESID' ] );
490
		} else {
491
			$session_id = md5( session_id() . get_current_blog_id() . $this->_get_sid_salt() );
492
		}
493
		return apply_filters( 'FHEE__EE_Session___generate_session_id__session_id', $session_id );
494
	}
495
496
497
498
	 /**
499
	  * _get_sid_salt
500
	  *
501
	  * @return string
502
	  */
503
	protected function _get_sid_salt() {
504
		// was session id salt already saved to db ?
505
		if ( empty( $this->_sid_salt ) ) {
506
			// no?  then maybe use WP defined constant
507
			if ( defined( 'AUTH_SALT' ) ) {
508
				$this->_sid_salt = AUTH_SALT;
509
			}
510
			// if salt doesn't exist or is too short
511
			if ( empty( $this->_sid_salt ) || strlen( $this->_sid_salt ) < 32 ) {
512
				// create a new one
513
				$this->_sid_salt = wp_generate_password( 64 );
514
			}
515
			// and save it as a permanent session setting
516
			$session_settings = get_option( 'ee_session_settings' );
517
			$session_settings[ 'sid_salt' ] = $this->_sid_salt;
518
			update_option( 'ee_session_settings', $session_settings );
519
		}
520
		return $this->_sid_salt;
521
	}
522
523
524
525
	 /**
526
	  * _set_init_access_and_expiration
527
	  * @return void
528
	  */
529
	protected function _set_init_access_and_expiration() {
530
		$this->_time = time();
531
		$this->_expiration = $this->_time + $this->_lifespan;
532
		// set initial site access time
533
		$this->_session_data['init_access'] = $this->_time;
534
		// and the session expiration
535
		$this->_session_data['expiration'] = $this->_expiration;
536
	}
537
538
539
540
	 /**
541
	  * @update session data  prior to saving to the db
542
	  * @access public
543
	  * @param bool $new_session
544
	  * @return TRUE on success, FALSE on fail
545
	  */
546
	public function update( $new_session = FALSE ) {
547
		$this->_session_data = isset( $this->_session_data )
0 ignored issues
show
Documentation Bug introduced by
It seems like isset($this->_session_da...s->_session_data : NULL can be null. However, the property $_session_data is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
548
			&& is_array( $this->_session_data )
549
			&& isset( $this->_session_data['id'])
550
			? $this->_session_data
551
			: NULL;
552
		if ( empty( $this->_session_data )) {
553
			$this->_set_defaults();
554
		}
555
		$session_data = array();
556
		foreach ( $this->_session_data as $key => $value ) {
0 ignored issues
show
Bug introduced by
The expression $this->_session_data of type array<string,?,{"id":"?"}>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
557
558
			switch( $key ) {
559
560
				case 'id' :
561
					// session ID
562
					$session_data['id'] = $this->_sid;
563
				break;
564
565
				case 'ip_address' :
566
					// visitor ip address
567
					$session_data['ip_address'] = $this->_visitor_ip();
568
				break;
569
570
				case 'user_agent' :
571
					// visitor user_agent
572
					$session_data['user_agent'] = $this->_user_agent;
573
				break;
574
575
				case 'init_access' :
576
					$session_data['init_access'] = absint( $value );
577
				break;
578
579
				case 'last_access' :
580
					// current access time
581
					$session_data['last_access'] = $this->_time;
582
				break;
583
584
				case 'expiration' :
585
					// when the session expires
586
					$session_data['expiration'] = ! empty( $this->_expiration )
587
						? $this->_expiration
588
						: $session_data['init_access'] + $this->_lifespan;
589
				break;
590
591
				case 'user_id' :
592
					// current user if logged in
593
					$session_data['user_id'] = $this->_wp_user_id();
594
				break;
595
596
				case 'pages_visited' :
597
					$page_visit = $this->_get_page_visit();
598
					if ( $page_visit ) {
599
						// set pages visited where the first will be the http referrer
600
						$this->_session_data[ 'pages_visited' ][ $this->_time ] = $page_visit;
601
						// we'll only save the last 10 page visits.
602
						$session_data[ 'pages_visited' ] = array_slice( $this->_session_data['pages_visited'], -10 );
603
					}
604
				break;
605
606
				default :
607
					// carry any other data over
608
					$session_data[$key] = $this->_session_data[$key];
609
610
			}
611
612
		}
613
614
		$this->_session_data = $session_data;
615
		// creating a new session does not require saving to the db just yet
616
		if ( ! $new_session ) {
617
			// ready? let's save
618
			if ( $this->_save_session_to_db() ) {
619
				return TRUE;
620
			} else {
621
				return FALSE;
622
			}
623
		}
624
		// meh, why not?
625
		return TRUE;
626
627
	}
628
629
630
631
	 /**
632
	  * since WordPress has no do_action()s within wp_safe_redirect,
633
	  * we have to hack into one of the supplied filters
634
	  * in order to make sure the session is updated prior to redirecting.
635
	  * This is a callback for the 'wp_redirect' filter
636
	  *
637
	  * @param string $location
638
	  * @return mixed
639
	  */
640
	 public function update_on_redirect( $location ) {
641
		 $this->update();
642
		 return $location;
643
	}
644
645
646
	/**
647
	 * 	@create session data array
648
	 * 	@access public
649
	 * 	@return bool
650
	 */
651
	private function _create_espresso_session( ) {
652
		do_action( 'AHEE_log', __CLASS__, __FUNCTION__, '' );
653
		// use the update function for now with $new_session arg set to TRUE
654
		return  $this->update( TRUE ) ? TRUE : FALSE;
655
	}
656
657
658
659
660
661
	/**
662
	 * _save_session_to_db
663
	 *
664
	 * 	@access public
665
	 * 	@return string
666
	 */
667
	private function _save_session_to_db() {
668
		if (
669
			// if the current request is NOT one of the following
670
			! (
671
				(
672
					// an espresso page
673
					EE_Registry::instance()->REQ instanceof EE_Request_Handler
674
					&& EE_Registry::instance()->REQ->is_espresso_page()
675
				)
676
				// OR an an AJAX request from the frontend
677
				|| EE_Registry::instance()->REQ->front_ajax
678
				// OR an admin request that is NOT AJAX
679
				|| (
680
					is_admin()
681
					&& ! ( defined( 'DOING_AJAX' ) && DOING_AJAX )
682
				)
683
			)
684
		) {
685
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by EE_Session::_save_session_to_db of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
686
		}
687
        // then serialize all of our session data
688
        $session_data = serialize($this->_session_data);
689
        // do we need to also encode it to avoid corrupted data when saved to the db?
690
        if (EE_Registry::instance()->CFG->admin->encode_session_data()) {
691
            $session_data = base64_encode($session_data);
692
        }
693
		// encrypt it if we are using encryption
694
		$session_data = $this->_use_encryption ? $this->encryption->encrypt( $session_data ) : $session_data;
695
		// maybe save hash check
696
		if ( apply_filters( 'FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG ) ) {
697
			set_transient( EE_Session::hash_check_prefix . $this->_sid, md5( $session_data ), $this->_lifespan );
698
		}
699
		// we're using the Transient API for storing session data, cuz it's so damn simple -> set_transient(  transient ID, data, expiry )
700
		return set_transient( EE_Session::session_id_prefix . $this->_sid, $session_data, $this->_lifespan );
701
	}
702
703
704
705
706
707
	/**
708
	 * _visitor_ip
709
	 *	attempt to get IP address of current visitor from server
710
	 * plz see: http://stackoverflow.com/a/2031935/1475279
711
	 *
712
	 *	@access public
713
	 *	@return string
714
	 */
715 View Code Duplication
	private function _visitor_ip() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
716
		$visitor_ip = '0.0.0.0';
717
		$server_keys = array(
718
			'HTTP_CLIENT_IP',
719
			'HTTP_X_FORWARDED_FOR',
720
			'HTTP_X_FORWARDED',
721
			'HTTP_X_CLUSTER_CLIENT_IP',
722
			'HTTP_FORWARDED_FOR',
723
			'HTTP_FORWARDED',
724
			'REMOTE_ADDR'
725
		);
726
		foreach ( $server_keys as $key ){
727
			if ( isset( $_SERVER[ $key ] )) {
728
				foreach ( array_map( 'trim', explode( ',', $_SERVER[ $key ] )) as $ip ) {
729
					if ( $ip === '127.0.0.1' || filter_var( $ip, FILTER_VALIDATE_IP ) !== FALSE ) {
730
						$visitor_ip = $ip;
731
					}
732
				}
733
			}
734
		}
735
		return $visitor_ip;
736
	}
737
738
739
740
741
742
	/**
743
	 *			@get the full page request the visitor is accessing
744
	 *		  	@access public
745
	 *			@return string
746
	 */
747
	public function _get_page_visit() {
748
		$page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
749
		// check for request url
750
		if ( isset( $_SERVER['REQUEST_URI'] )) {
751
			$http_host = '';
752
			$page_id = '?';
753
			$e_reg = '';
754
			$request_uri = esc_url( $_SERVER['REQUEST_URI'] );
755
			$ru_bits = explode( '?', $request_uri );
756
			$request_uri = $ru_bits[0];
757
			// check for and grab host as well
758
			if ( isset( $_SERVER['HTTP_HOST'] )) {
759
				$http_host = esc_url( $_SERVER['HTTP_HOST'] );
760
			}
761
			// check for page_id in SERVER REQUEST
762
			if ( isset( $_REQUEST['page_id'] )) {
763
				// rebuild $e_reg without any of the extra parameters
764
				$page_id = '?page_id=' . esc_attr( $_REQUEST['page_id'] ) . '&amp;';
765
			}
766
			// check for $e_reg in SERVER REQUEST
767
			if ( isset( $_REQUEST['ee'] )) {
768
				// rebuild $e_reg without any of the extra parameters
769
				$e_reg = 'ee=' . esc_attr( $_REQUEST['ee'] );
770
			}
771
			$page_visit = rtrim( $http_host . $request_uri . $page_id . $e_reg, '?' );
772
		}
773
		return $page_visit !== home_url( '/wp-admin/admin-ajax.php' ) ? $page_visit : '';
774
775
	}
776
777
778
779
780
781
	/**
782
	 * 	@the current wp user id
783
	 * 	@access public
784
	 * 	@return int
785
	 */
786
	public function _wp_user_id() {
787
		// if I need to explain the following lines of code, then you shouldn't be looking at this!
788
		$this->_wp_user_id = get_current_user_id();
789
		return $this->_wp_user_id;
790
	}
791
792
793
794
	 /**
795
	  * Clear EE_Session data
796
	  *
797
	  * @access public
798
	  * @param string $class
799
	  * @param string $function
800
	  * @return void
801
	  */
802
	public function clear_session( $class = '', $function = '' ) {
803
		//echo '<h3 style="color:#999;line-height:.9em;"><span style="color:#2EA2CC">' . __CLASS__ . '</span>::<span style="color:#E76700">' . __FUNCTION__ . '( ' . $class . '::' . $function . '() )</span><br/><span style="font-size:9px;font-weight:normal;">' . __FILE__ . '</span>    <b style="font-size:10px;">  ' . __LINE__ . ' </b></h3>';
804
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' .  $function . '()' );
805
		$this->reset_cart();
806
		$this->reset_checkout();
807
		$this->reset_transaction();
808
		// wipe out everything that isn't a default session datum
809
		$this->reset_data( array_keys( $this->_session_data ));
810
		// reset initial site access time and the session expiration
811
		$this->_set_init_access_and_expiration();
812
		$this->_save_session_to_db();
813
	}
814
815
816
817
	 /**
818
	  * @resets all non-default session vars
819
	  * @access public
820
	  * @param array $data_to_reset
821
	  * @param bool  $show_all_notices
822
	  * @return TRUE on success, FALSE on fail
823
	  */
824
	public function reset_data( $data_to_reset = array(), $show_all_notices = FALSE ) {
825
		// if $data_to_reset is not in an array, then put it in one
826
		if ( ! is_array( $data_to_reset ) ) {
827
			$data_to_reset = array ( $data_to_reset );
828
		}
829
		// nothing ??? go home!
830
		if ( empty( $data_to_reset )) {
831
			EE_Error::add_error( __( 'No session data could be reset, because no session var name was provided.', 'event_espresso' ), __FILE__, __FUNCTION__, __LINE__ );
832
			return FALSE;
833
		}
834
		$return_value = TRUE;
835
		// since $data_to_reset is an array, cycle through the values
836
		foreach ( $data_to_reset as $reset ) {
837
838
			// first check to make sure it is a valid session var
839
			if ( isset( $this->_session_data[ $reset ] )) {
840
				// then check to make sure it is not a default var
841
				if ( ! array_key_exists( $reset, $this->_default_session_vars )) {
842
					// remove session var
843
					unset( $this->_session_data[ $reset ] );
844
					if ( $show_all_notices ) {
845
						EE_Error::add_success( sprintf( __( 'The session variable %s was removed.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
846
					}
847
					$return_value = !isset($return_value) ? TRUE : $return_value;
848
849 View Code Duplication
				} else {
850
					// yeeeeeeeeerrrrrrrrrrr OUT !!!!
851
					if ( $show_all_notices ) {
852
						EE_Error::add_error( sprintf( __( 'Sorry! %s is a default session datum and can not be reset.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
853
					}
854
					$return_value = FALSE;
855
				}
856
857 View Code Duplication
			} else if ( $show_all_notices ) {
858
				// oops! that session var does not exist!
859
				EE_Error::add_error( sprintf( __( 'The session item provided, %s, is invalid or does not exist.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
860
				$return_value = FALSE;
861
			}
862
863
		} // end of foreach
864
865
		return $return_value;
866
867
	}
868
869
870
871
872
873
874
	/**
875
	 *   wp_loaded
876
	 *   @access public
877
	 */
878
	public function wp_loaded() {
879
		if ( isset(  EE_Registry::instance()->REQ ) && EE_Registry::instance()->REQ->is_set( 'clear_session' )) {
880
			$this->clear_session( __CLASS__, __FUNCTION__ );
881
		}
882
	}
883
884
885
886
	/**
887
	 * Used to reset the entire object (for tests).
888
	 *
889
	 * @since 4.3.0
890
	 *
891
	 */
892
	public function reset_instance() {
893
		$this->clear_session();
894
		self::$_instance = NULL;
895
	}
896
897
898
899
	 /**
900
	  * garbage_collection
901
	  * @since 4.3.0
902
	  */
903
	 public function garbage_collection() {
904
		 // only perform during regular requests
905
		 if ( ! defined( 'DOING_AJAX') || ! DOING_AJAX ) {
906
			 /** @type WPDB $wpdb */
907
			 global $wpdb;
908
			 // since transient expiration timestamps are set in the future, we can compare against NOW
909
			 $expiration = time();
910
			 $too_far_in_the_the_future = $expiration + ( $this->_lifespan * 2 );
911
			 // filter the query limit. Set to 0 to turn off garbage collection
912
			 $expired_session_transient_delete_query_limit = absint( apply_filters( 'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit', 50 ));
913
			 // non-zero LIMIT means take out the trash
914
			 if ( $expired_session_transient_delete_query_limit ) {
915
				 //array of transient keys that require garbage collection
916
				 $session_keys = array(
917
					 EE_Session::session_id_prefix,
918
					 EE_Session::hash_check_prefix,
919
				 );
920
				 foreach ( $session_keys as $session_key ) {
921
					 $session_key = str_replace( '_', '\_', $session_key );
922
					 $session_key = '\_transient\_timeout\_' . $session_key . '%';
923
					 $SQL = "
924
					SELECT option_name
925
					FROM {$wpdb->options}
926
					WHERE option_name
927
					LIKE '{$session_key}'
928
					AND ( option_value < {$expiration}
929
					OR option_value > {$too_far_in_the_the_future} )
930
					LIMIT {$expired_session_transient_delete_query_limit}
931
				";
932
					 $expired_sessions = $wpdb->get_col( $SQL );
933
					 // valid results?
934
					 if ( ! $expired_sessions instanceof WP_Error && ! empty( $expired_sessions ) ) {
0 ignored issues
show
Bug introduced by
The class WP_Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
935
						 // format array of results into something usable within the actual DELETE query's IN clause
936
						 $expired = array();
937
						 foreach ( $expired_sessions as $expired_session ) {
938
							 $expired[ ] = "'" . $expired_session . "'";
939
							 $expired[ ] = "'" . str_replace( 'timeout_', '', $expired_session ) . "'";
940
						 }
941
						 $expired = implode( ', ', $expired );
942
						 $SQL = "
943
						DELETE FROM {$wpdb->options}
944
						WHERE option_name
945
						IN ( $expired );
946
					 ";
947
						 $results = $wpdb->query( $SQL );
948
						 // if something went wrong, then notify the admin
949
						 if ( $results instanceof WP_Error && is_admin() ) {
0 ignored issues
show
Bug introduced by
The class WP_Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
950
							 EE_Error::add_error( $results->get_error_message(), __FILE__, __FUNCTION__, __LINE__ );
951
						 }
952
					 }
953
				 }
954
				 do_action(
955
					 'FHEE__EE_Session__garbage_collection___end',
956
					 $expired_session_transient_delete_query_limit
957
				 );
958
			 }
959
		 }
960
961
962
	 }
963
964
965
966
	 /**
967
	  * @see http://stackoverflow.com/questions/2556345/detect-base64-encoding-in-php#30231906
968
	  * @param $string
969
	  * @return bool
970
	  */
971
	 private function valid_base_64( $string ) {
972
		 $decoded = base64_decode( $string, true );
973
		 // Check if there is no invalid character in string
974
		 if ( ! preg_match( '/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $string ) ) {
975
			 return false;
976
		 }
977
		 // Decode the string in strict mode and send the response
978
		 if ( ! base64_decode( $string, true ) ) {
979
			 return false;
980
		 }
981
		 // Encode and compare it to original one
982
		 return base64_encode( $decoded ) === $string;
983
	 }
984
985
986
987
	 /**
988
	  * @see http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset/21389439#10152996
989
	  * @param $data1
990
	  * @return string
991
	  */
992
	 private function find_serialize_error( $data1 ) {
993
		$error = "<pre>";
994
		 $data2 = preg_replace_callback(
995
			 '!s:(\d+):"(.*?)";!',
996
			 function ( $match ) {
997
				 return ( $match[1] === strlen( $match[2] ) )
998
					 ? $match[0]
999
					 : 's:'
1000
					   . strlen( $match[2] )
1001
					   . ':"'
1002
					   . $match[2]
1003
					   . '";';
1004
			 },
1005
			 $data1
1006
		 );
1007
		$max = ( strlen( $data1 ) > strlen( $data2 ) ) ? strlen( $data1 ) : strlen( $data2 );
1008
		$error .= $data1 . PHP_EOL;
1009
		$error .= $data2 . PHP_EOL;
1010
		for ( $i = 0; $i < $max; $i++ ) {
1011
			if ( @$data1[ $i ] !== @$data2[ $i ] ) {
1012
				$error .= "Difference " . @$data1[ $i ] . " != " . @$data2[ $i ] . PHP_EOL;
1013
				$error .= "\t-> ORD number " . ord( @$data1[ $i ] ) . " != " . ord( @$data2[ $i ] ) . PHP_EOL;
1014
				$error .= "\t-> Line Number = $i" . PHP_EOL;
1015
				$start = ( $i - 20 );
1016
				$start = ( $start < 0 ) ? 0 : $start;
1017
				$length = 40;
1018
				$point = $max - $i;
1019
				if ( $point < 20 ) {
1020
					$rlength = 1;
1021
					$rpoint = -$point;
1022
				} else {
1023
					$rpoint = $length - 20;
1024
					$rlength = 1;
1025
				}
1026
				$error .= "\t-> Section Data1  = ";
1027
				$error .= substr_replace(
1028
					substr( $data1, $start, $length ),
1029
					"<b style=\"color:green\">{$data1[ $i ]}</b>",
1030
					$rpoint,
1031
					$rlength
1032
				);
1033
				$error .= PHP_EOL;
1034
				$error .= "\t-> Section Data2  = ";
1035
				$error .= substr_replace(
1036
					substr( $data2, $start, $length ),
1037
					"<b style=\"color:red\">{$data2[ $i ]}</b>",
1038
					$rpoint,
1039
					$rlength
1040
				);
1041
				$error .= PHP_EOL;
1042
			}
1043
		}
1044
		$error .= "</pre>";
1045
		return $error;
1046
	}
1047
1048
 }
1049
/* End of file EE_Session.class.php */
1050
/* Location: /includes/classes/EE_Session.class.php */
1051