Completed
Branch BUG-10209-session-encoding (73bea7)
by
unknown
194:52 queued 176:07
created

EE_Session::open_session()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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 = (array) get_option( 'ee_session_settings', array() );
158
		if ( ! empty( $session_settings )) {
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
         $this->_use_encryption = $encryption instanceof EE_Encryption && EE_Registry::instance()->CFG->admin->encode_session_data();
168
         // \EEH_Debug_Tools::printr($this->_use_encryption, '$this->_use_encryption', __FILE__, __LINE__);
169
        // encrypt data via: $this->encryption->encrypt();
170
        $this->encryption = $encryption;
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
         add_action('AHEE__EE_System__initialize', array($this, 'open_session'));
177
         // check request for 'clear_session' param
178
		add_action( 'AHEE__EE_Request_Handler__construct__complete', array( $this, 'wp_loaded' ));
179
		// once everything is all said and done,
180
		add_action( 'shutdown', array( $this, 'update' ), 100 );
181
		add_action( 'shutdown', array( $this, 'garbage_collection' ), 999 );
182
		add_filter( 'wp_redirect', array( $this, 'update_on_redirect' ), 100, 1 );
183
	}
184
185
186
187
     /**
188
      * @return void
189
      * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
190
      * @throws \EE_Error
191
      */
192
	 public function open_session() {
193
         // check for existing session and retrieve it from db
194
         if ( ! $this->_espresso_session()) {
195
             // or just start a new one
196
             $this->_create_espresso_session();
197
         }
198
     }
199
200
201
	 /**
202
	  * @return int
203
	  */
204
	 public function expiration() {
205
		 return $this->_expiration;
206
	 }
207
208
209
210
	 /**
211
	  * @return int
212
	  */
213
	 public function lifespan() {
214
		 return $this->_lifespan;
215
	 }
216
217
218
219
	/**
220
	 * This just sets some defaults for the _session data property
221
	 *
222
	 * @access private
223
	 * @return void
224
	 */
225
	private function _set_defaults() {
226
		// set some defaults
227
		foreach ( $this->_default_session_vars as $key => $default_var ) {
228
			if ( is_array( $default_var )) {
229
				$this->_session_data[ $key ] = array();
230
			} else {
231
				$this->_session_data[ $key ] = '';
232
			}
233
		}
234
	}
235
236
237
238
	/**
239
	 * @retrieve session data
240
	 * @access	public
241
	 * @return	string
242
	 */
243
	public function id() {
244
		return $this->_sid;
245
	}
246
247
248
249
	 /**
250
	  * @param \EE_Cart $cart
251
	  * @return bool
252
	  */
253
	 public function set_cart( EE_Cart $cart ) {
254
		 $this->_session_data['cart'] = $cart;
255
		 return TRUE;
256
	 }
257
258
259
260
	 /**
261
	  * reset_cart
262
	  */
263
	 public function reset_cart() {
264
		 $this->_session_data['cart'] = NULL;
265
	 }
266
267
268
269
	 /**
270
	  * @return \EE_Cart
271
	  */
272
	 public function cart() {
273
		 return isset( $this->_session_data['cart'] ) ? $this->_session_data['cart'] : NULL;
274
	 }
275
276
277
278
	 /**
279
	  * @param \EE_Checkout $checkout
280
	  * @return bool
281
	  */
282
	 public function set_checkout( EE_Checkout $checkout ) {
283
		 $this->_session_data['checkout'] = $checkout;
284
		 return TRUE;
285
	 }
286
287
288
289
	 /**
290
	  * reset_checkout
291
	  */
292
	 public function reset_checkout() {
293
		 $this->_session_data['checkout'] = NULL;
294
	 }
295
296
297
298
	 /**
299
	  * @return \EE_Checkout
300
	  */
301
	 public function checkout() {
302
		 return isset( $this->_session_data['checkout'] ) ? $this->_session_data['checkout'] : NULL;
303
	 }
304
305
306
307
	 /**
308
	  * @param \EE_Transaction $transaction
309
	  * @return bool
310
	  * @throws \EE_Error
311
	  */
312
	 public function set_transaction( EE_Transaction $transaction ) {
313
		 // first remove the session from the transaction before we save the transaction in the session
314
		 $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...
315
		 $this->_session_data['transaction'] = $transaction;
316
		 return TRUE;
317
	 }
318
319
320
321
	 /**
322
	  * reset_transaction
323
	  */
324
	 public function reset_transaction() {
325
		 $this->_session_data['transaction'] = NULL;
326
	 }
327
328
329
330
	 /**
331
	  * @return \EE_Transaction
332
	  */
333
	 public function transaction() {
334
		 return isset( $this->_session_data['transaction'] ) ? $this->_session_data['transaction'] : NULL;
335
	 }
336
337
338
339
	 /**
340
	  * retrieve session data
341
	  * @access    public
342
	  * @param null $key
343
	  * @param bool $reset_cache
344
	  * @return    array
345
	  */
346
	public function get_session_data( $key = NULL, $reset_cache = FALSE ) {
347
		if ( $reset_cache ) {
348
			$this->reset_cart();
349
			$this->reset_checkout();
350
			$this->reset_transaction();
351
		}
352
		 if ( ! empty( $key ))  {
353
			return  isset( $this->_session_data[ $key ] ) ? $this->_session_data[ $key ] : NULL;
354
		}  else  {
355
			return $this->_session_data;
356
		}
357
	}
358
359
360
361
	 /**
362
	  * set session data
363
	  * @access 	public
364
	  * @param 	array $data
365
	  * @return 	TRUE on success, FALSE on fail
366
	  */
367
	public function set_session_data( $data ) {
368
369
		// nothing ??? bad data ??? go home!
370 View Code Duplication
		if ( empty( $data ) || ! is_array( $data )) {
371
			EE_Error::add_error( __( 'No session data or invalid session data was provided.', 'event_espresso' ), __FILE__, __FUNCTION__, __LINE__ );
372
			return FALSE;
373
		}
374
375
		foreach ( $data as $key =>$value ) {
376
			if ( isset( $this->_default_session_vars[ $key ] )) {
377
				EE_Error::add_error( sprintf( __( 'Sorry! %s is a default session datum and can not be reset.', 'event_espresso' ), $key ), __FILE__, __FUNCTION__, __LINE__ );
378
				return FALSE;
379
			} else {
380
				$this->_session_data[ $key ] = $value;
381
			}
382
		}
383
384
		return TRUE;
385
386
	}
387
388
389
390
	 /**
391
	  * @initiate session
392
	  * @access   private
393
	  * @return TRUE on success, FALSE on fail
394
	  * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
395
	  * @throws \EE_Error
396
	  */
397
	private function _espresso_session() {
398
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, '' );
399
		// check that session has started
400
		if ( session_id() === '' ) {
401
			//starts a new session if one doesn't already exist, or re-initiates an existing one
402
			session_start();
403
		}
404
		// get our modified session ID
405
		$this->_sid = $this->_generate_session_id();
406
		// and the visitors IP
407
		$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...
408
		// set the "user agent"
409
		$this->_user_agent = ( isset($_SERVER['HTTP_USER_AGENT'])) ? esc_attr( $_SERVER['HTTP_USER_AGENT'] ) : FALSE;
410
		// now let's retrieve what's in the db
411
        $session_data = $this->_retrieve_session_data();
412
        if (! empty($session_data)) {
413
            // get the current time in UTC
414
			$this->_time = isset( $this->_time ) ? $this->_time : time();
415
			// and reset the session expiration
416
			$this->_expiration = isset( $session_data['expiration'] )
417
				? $session_data['expiration']
418
				: $this->_time + $this->_lifespan;
419
		} else {
420
            // set initial site access time and the session expiration
421
			$this->_set_init_access_and_expiration();
422
			// set referer
423
			$this->_session_data[ 'pages_visited' ][ $this->_session_data['init_access'] ] = isset( $_SERVER['HTTP_REFERER'] )
424
				? esc_attr( $_SERVER['HTTP_REFERER'] )
425
				: '';
426
			// no previous session = go back and create one (on top of the data above)
427
			return FALSE;
428
		}
429
        // now the user agent
430
		if ( $session_data['user_agent'] !== $this->_user_agent ) {
431
			return FALSE;
432
		}
433
		// wait a minute... how old are you?
434
		if ( $this->_time > $this->_expiration ) {
435
			// yer too old fer me!
436
			// wipe out everything that isn't a default session datum
437
			$this->clear_session( __CLASS__, __FUNCTION__ );
438
		}
439
		// make event espresso session data available to plugin
440
		$this->_session_data = array_merge( $this->_session_data, $session_data );
441
		return TRUE;
442
443
	}
444
445
446
447
     /**
448
      * _get_session_data
449
      * Retrieves the session data, and attempts to correct any encoding issues that can occur due to improperly setup databases
450
      *
451
      * @return array
452
      * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
453
      */
454
     protected function _retrieve_session_data()
455
     {
456
         $ssn_key = EE_Session::session_id_prefix . $this->_sid;
457
         try {
458
             // we're using WP's Transient API to store session data using the PHP session ID as the option name
459
             $session_data = get_transient($ssn_key);
460
             if ($session_data === false) {
461
                 return array();
462
             }
463
             if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
464
                 $hash_check = get_transient(EE_Session::hash_check_prefix . $this->_sid);
465
                 if ($hash_check && $hash_check !== md5($session_data)) {
466
                     EE_Error::add_error(
467
                         sprintf(
468
                             __('The stored data for session %1$s failed to pass a hash check and therefore appears to be invalid.', 'event_espresso'),
469
                             EE_Session::session_id_prefix . $this->_sid
470
                         ),
471
                         __FILE__, __FUNCTION__, __LINE__
472
                     );
473
                 }
474
             }
475
         } catch (Exception $e) {
476
             // let's just eat that error for now and attempt to correct any corrupted data
477
             global $wpdb;
478
             $row = $wpdb->get_row(
479
                 $wpdb->prepare(
480
                     "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
481
                     '_transient_' . $ssn_key
482
                 )
483
             );
484
             $session_data = is_object($row) ? $row->option_value : null;
485
             if ($session_data) {
486
                 $session_data = preg_replace_callback(
487
                     '!s:(d+):"(.*?)";!',
488 View Code Duplication
                     function ($match) {
489
                         return $match[1] === strlen($match[2])
490
                             ? $match[0]
491
                             : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
492
                     },
493
                     $session_data
494
                 );
495
             }
496
             $session_data = maybe_unserialize($session_data);
497
         }
498
         // in case the data is encoded... try to decode it
499
         $session_data = $this->encryption instanceof EE_Encryption
500
             ? $this->encryption->base64_string_decode($session_data)
501
             : $session_data;
502
         if ( ! is_array($session_data)) {
503
             try {
504
                 $session_data = maybe_unserialize($session_data);
505
             } catch (Exception $e) {
506
                 $msg = esc_html__(
507
                     'An error occurred while attempting to unserialize the session data.',
508
                     'event_espresso'
509
                 );
510
                 $msg .= WP_DEBUG
511
                     ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
512
                     : '';
513
                 throw new InvalidSessionDataException($msg, 0, $e);
514
             }
515
         }
516
         // just a check to make sure the session array is indeed an array
517
         if ( ! is_array($session_data)) {
518
             // no?!?! then something is wrong
519
             $msg = esc_html__(
520
                 'The session data is missing, invalid, or corrupted.',
521
                 'event_espresso'
522
             );
523
             $msg .= WP_DEBUG
524
                 ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
525
                 : '';
526
             throw new InvalidSessionDataException($msg);
527
         }
528
         return $session_data;
529
     }
530
531
532
533
	 /**
534
	  * _generate_session_id
535
	  * Retrieves the PHP session id either directly from the PHP session,
536
	  * or from the $_REQUEST array if it was passed in from an AJAX request.
537
	  * The session id is then salted and hashed (mmm sounds tasty)
538
	  * so that it can be safely used as a $_REQUEST param
539
	  *
540
	  * @return string
541
	  */
542
	protected function _generate_session_id() {
543
		// check if the SID was passed explicitly, otherwise get from session, then add salt and hash it to reduce length
544
		if ( isset( $_REQUEST[ 'EESID' ] ) ) {
545
			$session_id = sanitize_text_field( $_REQUEST[ 'EESID' ] );
546
		} else {
547
			$session_id = md5( session_id() . get_current_blog_id() . $this->_get_sid_salt() );
548
		}
549
		return apply_filters( 'FHEE__EE_Session___generate_session_id__session_id', $session_id );
550
	}
551
552
553
554
	 /**
555
	  * _get_sid_salt
556
	  *
557
	  * @return string
558
	  */
559
	protected function _get_sid_salt() {
560
		// was session id salt already saved to db ?
561
		if ( empty( $this->_sid_salt ) ) {
562
			// no?  then maybe use WP defined constant
563
			if ( defined( 'AUTH_SALT' ) ) {
564
				$this->_sid_salt = AUTH_SALT;
565
			}
566
			// if salt doesn't exist or is too short
567
			if ( empty( $this->_sid_salt ) || strlen( $this->_sid_salt ) < 32 ) {
568
				// create a new one
569
				$this->_sid_salt = wp_generate_password( 64 );
570
			}
571
			// and save it as a permanent session setting
572
			$session_settings = get_option( 'ee_session_settings' );
573
			$session_settings[ 'sid_salt' ] = $this->_sid_salt;
574
			update_option( 'ee_session_settings', $session_settings );
575
		}
576
		return $this->_sid_salt;
577
	}
578
579
580
581
	 /**
582
	  * _set_init_access_and_expiration
583
	  * @return void
584
	  */
585
	protected function _set_init_access_and_expiration() {
586
		$this->_time = time();
587
		$this->_expiration = $this->_time + $this->_lifespan;
588
		// set initial site access time
589
		$this->_session_data['init_access'] = $this->_time;
590
		// and the session expiration
591
		$this->_session_data['expiration'] = $this->_expiration;
592
	}
593
594
595
596
	 /**
597
	  * @update session data  prior to saving to the db
598
	  * @access public
599
	  * @param bool $new_session
600
	  * @return TRUE on success, FALSE on fail
601
	  */
602
	public function update( $new_session = FALSE ) {
603
		$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...
604
			&& is_array( $this->_session_data )
605
			&& isset( $this->_session_data['id'])
606
			? $this->_session_data
607
			: NULL;
608
		if ( empty( $this->_session_data )) {
609
			$this->_set_defaults();
610
		}
611
		$session_data = array();
612
		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...
613
614
			switch( $key ) {
615
616
				case 'id' :
617
					// session ID
618
					$session_data['id'] = $this->_sid;
619
				break;
620
621
				case 'ip_address' :
622
					// visitor ip address
623
					$session_data['ip_address'] = $this->_visitor_ip();
624
				break;
625
626
				case 'user_agent' :
627
					// visitor user_agent
628
					$session_data['user_agent'] = $this->_user_agent;
629
				break;
630
631
				case 'init_access' :
632
					$session_data['init_access'] = absint( $value );
633
				break;
634
635
				case 'last_access' :
636
					// current access time
637
					$session_data['last_access'] = $this->_time;
638
				break;
639
640
				case 'expiration' :
641
					// when the session expires
642
					$session_data['expiration'] = ! empty( $this->_expiration )
643
						? $this->_expiration
644
						: $session_data['init_access'] + $this->_lifespan;
645
				break;
646
647
				case 'user_id' :
648
					// current user if logged in
649
					$session_data['user_id'] = $this->_wp_user_id();
650
				break;
651
652
				case 'pages_visited' :
653
					$page_visit = $this->_get_page_visit();
654
					if ( $page_visit ) {
655
						// set pages visited where the first will be the http referrer
656
						$this->_session_data[ 'pages_visited' ][ $this->_time ] = $page_visit;
657
						// we'll only save the last 10 page visits.
658
						$session_data[ 'pages_visited' ] = array_slice( $this->_session_data['pages_visited'], -10 );
659
					}
660
				break;
661
662
				default :
663
					// carry any other data over
664
					$session_data[$key] = $this->_session_data[$key];
665
666
			}
667
668
		}
669
670
		$this->_session_data = $session_data;
671
		// creating a new session does not require saving to the db just yet
672
		if ( ! $new_session ) {
673
			// ready? let's save
674
			if ( $this->_save_session_to_db() ) {
675
				return TRUE;
676
			} else {
677
				return FALSE;
678
			}
679
		}
680
		// meh, why not?
681
		return TRUE;
682
683
	}
684
685
686
687
	 /**
688
	  * since WordPress has no do_action()s within wp_safe_redirect,
689
	  * we have to hack into one of the supplied filters
690
	  * in order to make sure the session is updated prior to redirecting.
691
	  * This is a callback for the 'wp_redirect' filter
692
	  *
693
	  * @param string $location
694
	  * @return mixed
695
	  */
696
	 public function update_on_redirect( $location ) {
697
		 $this->update();
698
		 return $location;
699
	}
700
701
702
	/**
703
	 * 	@create session data array
704
	 * 	@access public
705
	 * 	@return bool
706
	 */
707
	private function _create_espresso_session( ) {
708
		do_action( 'AHEE_log', __CLASS__, __FUNCTION__, '' );
709
		// use the update function for now with $new_session arg set to TRUE
710
		return  $this->update( TRUE ) ? TRUE : FALSE;
711
	}
712
713
714
715
716
717
	/**
718
	 * _save_session_to_db
719
	 *
720
	 * 	@access public
721
	 * 	@return string
722
	 */
723
	private function _save_session_to_db() {
724
		if (
725
			// if the current request is NOT one of the following
726
			! (
727
				(
728
					// an espresso page
729
					EE_Registry::instance()->REQ instanceof EE_Request_Handler
730
					&& EE_Registry::instance()->REQ->is_espresso_page()
731
				)
732
				// OR an an AJAX request from the frontend
733
				|| EE_Registry::instance()->REQ->front_ajax
734
				// OR an admin request that is NOT AJAX
735
				|| (
736
					is_admin()
737
					&& ! ( defined( 'DOING_AJAX' ) && DOING_AJAX )
738
				)
739
			)
740
		) {
741
			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...
742
		}
743
        // then serialize all of our session data
744
        $session_data = serialize($this->_session_data);
745
        // do we need to also encode it to avoid corrupted data when saved to the db?
746
		$session_data = $this->_use_encryption ? $this->encryption->base64_string_encode( $session_data ) : $session_data;
747
		// maybe save hash check
748
		if ( apply_filters( 'FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG ) ) {
749
			set_transient( EE_Session::hash_check_prefix . $this->_sid, md5( $session_data ), $this->_lifespan );
750
		}
751
		// we're using the Transient API for storing session data, cuz it's so damn simple -> set_transient(  transient ID, data, expiry )
752
		return set_transient( EE_Session::session_id_prefix . $this->_sid, $session_data, $this->_lifespan );
753
	}
754
755
756
757
758
759
	/**
760
	 * _visitor_ip
761
	 *	attempt to get IP address of current visitor from server
762
	 * plz see: http://stackoverflow.com/a/2031935/1475279
763
	 *
764
	 *	@access public
765
	 *	@return string
766
	 */
767 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...
768
		$visitor_ip = '0.0.0.0';
769
		$server_keys = array(
770
			'HTTP_CLIENT_IP',
771
			'HTTP_X_FORWARDED_FOR',
772
			'HTTP_X_FORWARDED',
773
			'HTTP_X_CLUSTER_CLIENT_IP',
774
			'HTTP_FORWARDED_FOR',
775
			'HTTP_FORWARDED',
776
			'REMOTE_ADDR'
777
		);
778
		foreach ( $server_keys as $key ){
779
			if ( isset( $_SERVER[ $key ] )) {
780
				foreach ( array_map( 'trim', explode( ',', $_SERVER[ $key ] )) as $ip ) {
781
					if ( $ip === '127.0.0.1' || filter_var( $ip, FILTER_VALIDATE_IP ) !== FALSE ) {
782
						$visitor_ip = $ip;
783
					}
784
				}
785
			}
786
		}
787
		return $visitor_ip;
788
	}
789
790
791
792
793
794
	/**
795
	 *			@get the full page request the visitor is accessing
796
	 *		  	@access public
797
	 *			@return string
798
	 */
799
	public function _get_page_visit() {
800
		$page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
801
		// check for request url
802
		if ( isset( $_SERVER['REQUEST_URI'] )) {
803
			$http_host = '';
804
			$page_id = '?';
805
			$e_reg = '';
806
			$request_uri = esc_url( $_SERVER['REQUEST_URI'] );
807
			$ru_bits = explode( '?', $request_uri );
808
			$request_uri = $ru_bits[0];
809
			// check for and grab host as well
810
			if ( isset( $_SERVER['HTTP_HOST'] )) {
811
				$http_host = esc_url( $_SERVER['HTTP_HOST'] );
812
			}
813
			// check for page_id in SERVER REQUEST
814
			if ( isset( $_REQUEST['page_id'] )) {
815
				// rebuild $e_reg without any of the extra parameters
816
				$page_id = '?page_id=' . esc_attr( $_REQUEST['page_id'] ) . '&amp;';
817
			}
818
			// check for $e_reg in SERVER REQUEST
819
			if ( isset( $_REQUEST['ee'] )) {
820
				// rebuild $e_reg without any of the extra parameters
821
				$e_reg = 'ee=' . esc_attr( $_REQUEST['ee'] );
822
			}
823
			$page_visit = rtrim( $http_host . $request_uri . $page_id . $e_reg, '?' );
824
		}
825
		return $page_visit !== home_url( '/wp-admin/admin-ajax.php' ) ? $page_visit : '';
826
827
	}
828
829
830
831
832
833
	/**
834
	 * 	@the current wp user id
835
	 * 	@access public
836
	 * 	@return int
837
	 */
838
	public function _wp_user_id() {
839
		// if I need to explain the following lines of code, then you shouldn't be looking at this!
840
		$this->_wp_user_id = get_current_user_id();
841
		return $this->_wp_user_id;
842
	}
843
844
845
846
	 /**
847
	  * Clear EE_Session data
848
	  *
849
	  * @access public
850
	  * @param string $class
851
	  * @param string $function
852
	  * @return void
853
	  */
854
	public function clear_session( $class = '', $function = '' ) {
855
		//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>';
856
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' .  $function . '()' );
857
		$this->reset_cart();
858
		$this->reset_checkout();
859
		$this->reset_transaction();
860
		// wipe out everything that isn't a default session datum
861
		$this->reset_data( array_keys( $this->_session_data ));
862
		// reset initial site access time and the session expiration
863
		$this->_set_init_access_and_expiration();
864
		$this->_save_session_to_db();
865
	}
866
867
868
869
	 /**
870
	  * @resets all non-default session vars
871
	  * @access public
872
	  * @param array $data_to_reset
873
	  * @param bool  $show_all_notices
874
	  * @return TRUE on success, FALSE on fail
875
	  */
876
	public function reset_data( $data_to_reset = array(), $show_all_notices = FALSE ) {
877
		// if $data_to_reset is not in an array, then put it in one
878
		if ( ! is_array( $data_to_reset ) ) {
879
			$data_to_reset = array ( $data_to_reset );
880
		}
881
		// nothing ??? go home!
882
		if ( empty( $data_to_reset )) {
883
			EE_Error::add_error( __( 'No session data could be reset, because no session var name was provided.', 'event_espresso' ), __FILE__, __FUNCTION__, __LINE__ );
884
			return FALSE;
885
		}
886
		$return_value = TRUE;
887
		// since $data_to_reset is an array, cycle through the values
888
		foreach ( $data_to_reset as $reset ) {
889
890
			// first check to make sure it is a valid session var
891
			if ( isset( $this->_session_data[ $reset ] )) {
892
				// then check to make sure it is not a default var
893
				if ( ! array_key_exists( $reset, $this->_default_session_vars )) {
894
					// remove session var
895
					unset( $this->_session_data[ $reset ] );
896
					if ( $show_all_notices ) {
897
						EE_Error::add_success( sprintf( __( 'The session variable %s was removed.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
898
					}
899
					$return_value = !isset($return_value) ? TRUE : $return_value;
900
901 View Code Duplication
				} else {
902
					// yeeeeeeeeerrrrrrrrrrr OUT !!!!
903
					if ( $show_all_notices ) {
904
						EE_Error::add_error( sprintf( __( 'Sorry! %s is a default session datum and can not be reset.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
905
					}
906
					$return_value = FALSE;
907
				}
908
909 View Code Duplication
			} else if ( $show_all_notices ) {
910
				// oops! that session var does not exist!
911
				EE_Error::add_error( sprintf( __( 'The session item provided, %s, is invalid or does not exist.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
912
				$return_value = FALSE;
913
			}
914
915
		} // end of foreach
916
917
		return $return_value;
918
919
	}
920
921
922
923
924
925
926
	/**
927
	 *   wp_loaded
928
	 *   @access public
929
	 */
930
	public function wp_loaded() {
931
		if ( isset(  EE_Registry::instance()->REQ ) && EE_Registry::instance()->REQ->is_set( 'clear_session' )) {
932
			$this->clear_session( __CLASS__, __FUNCTION__ );
933
		}
934
	}
935
936
937
938
	/**
939
	 * Used to reset the entire object (for tests).
940
	 *
941
	 * @since 4.3.0
942
	 *
943
	 */
944
	public function reset_instance() {
945
		$this->clear_session();
946
		self::$_instance = NULL;
947
	}
948
949
950
951
	 /**
952
	  * garbage_collection
953
	  * @since 4.3.0
954
	  */
955
	 public function garbage_collection() {
956
		 // only perform during regular requests
957
		 if ( ! defined( 'DOING_AJAX') || ! DOING_AJAX ) {
958
			 /** @type WPDB $wpdb */
959
			 global $wpdb;
960
			 // since transient expiration timestamps are set in the future, we can compare against NOW
961
			 $expiration = time();
962
			 $too_far_in_the_the_future = $expiration + ( $this->_lifespan * 2 );
963
			 // filter the query limit. Set to 0 to turn off garbage collection
964
			 $expired_session_transient_delete_query_limit = absint( apply_filters( 'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit', 50 ));
965
			 // non-zero LIMIT means take out the trash
966
			 if ( $expired_session_transient_delete_query_limit ) {
967
				 //array of transient keys that require garbage collection
968
				 $session_keys = array(
969
					 EE_Session::session_id_prefix,
970
					 EE_Session::hash_check_prefix,
971
				 );
972
				 foreach ( $session_keys as $session_key ) {
973
					 $session_key = str_replace( '_', '\_', $session_key );
974
					 $session_key = '\_transient\_timeout\_' . $session_key . '%';
975
					 $SQL = "
976
					SELECT option_name
977
					FROM {$wpdb->options}
978
					WHERE option_name
979
					LIKE '{$session_key}'
980
					AND ( option_value < {$expiration}
981
					OR option_value > {$too_far_in_the_the_future} )
982
					LIMIT {$expired_session_transient_delete_query_limit}
983
				";
984
					 $expired_sessions = $wpdb->get_col( $SQL );
985
					 // valid results?
986
					 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...
987
						 // format array of results into something usable within the actual DELETE query's IN clause
988
						 $expired = array();
989
						 foreach ( $expired_sessions as $expired_session ) {
990
							 $expired[ ] = "'" . $expired_session . "'";
991
							 $expired[ ] = "'" . str_replace( 'timeout_', '', $expired_session ) . "'";
992
						 }
993
						 $expired = implode( ', ', $expired );
994
						 $SQL = "
995
						DELETE FROM {$wpdb->options}
996
						WHERE option_name
997
						IN ( $expired );
998
					 ";
999
						 $results = $wpdb->query( $SQL );
1000
						 // if something went wrong, then notify the admin
1001
						 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...
1002
							 EE_Error::add_error( $results->get_error_message(), __FILE__, __FUNCTION__, __LINE__ );
1003
						 }
1004
					 }
1005
				 }
1006
				 do_action(
1007
					 'FHEE__EE_Session__garbage_collection___end',
1008
					 $expired_session_transient_delete_query_limit
1009
				 );
1010
			 }
1011
		 }
1012
1013
1014
	 }
1015
1016
1017
1018
	 /**
1019
	  * @see http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset/21389439#10152996
1020
	  * @param $data1
1021
	  * @return string
1022
	  */
1023
	 private function find_serialize_error( $data1 ) {
1024
		$error = "<pre>";
1025
		 $data2 = preg_replace_callback(
1026
			 '!s:(\d+):"(.*?)";!',
1027 View Code Duplication
			 function ( $match ) {
1028
				 return ( $match[1] === strlen( $match[2] ) )
1029
					 ? $match[0]
1030
					 : 's:'
1031
					   . strlen( $match[2] )
1032
					   . ':"'
1033
					   . $match[2]
1034
					   . '";';
1035
			 },
1036
			 $data1
1037
		 );
1038
		$max = ( strlen( $data1 ) > strlen( $data2 ) ) ? strlen( $data1 ) : strlen( $data2 );
1039
		$error .= $data1 . PHP_EOL;
1040
		$error .= $data2 . PHP_EOL;
1041
		for ( $i = 0; $i < $max; $i++ ) {
1042
			if ( @$data1[ $i ] !== @$data2[ $i ] ) {
1043
				$error .= "Difference " . @$data1[ $i ] . " != " . @$data2[ $i ] . PHP_EOL;
1044
				$error .= "\t-> ORD number " . ord( @$data1[ $i ] ) . " != " . ord( @$data2[ $i ] ) . PHP_EOL;
1045
				$error .= "\t-> Line Number = $i" . PHP_EOL;
1046
				$start = ( $i - 20 );
1047
				$start = ( $start < 0 ) ? 0 : $start;
1048
				$length = 40;
1049
				$point = $max - $i;
1050
				if ( $point < 20 ) {
1051
					$rlength = 1;
1052
					$rpoint = -$point;
1053
				} else {
1054
					$rpoint = $length - 20;
1055
					$rlength = 1;
1056
				}
1057
				$error .= "\t-> Section Data1  = ";
1058
				$error .= substr_replace(
1059
					substr( $data1, $start, $length ),
1060
					"<b style=\"color:green\">{$data1[ $i ]}</b>",
1061
					$rpoint,
1062
					$rlength
1063
				);
1064
				$error .= PHP_EOL;
1065
				$error .= "\t-> Section Data2  = ";
1066
				$error .= substr_replace(
1067
					substr( $data2, $start, $length ),
1068
					"<b style=\"color:red\">{$data2[ $i ]}</b>",
1069
					$rpoint,
1070
					$rlength
1071
				);
1072
				$error .= PHP_EOL;
1073
			}
1074
		}
1075
		$error .= "</pre>";
1076
		return $error;
1077
	}
1078
1079
 }
1080
/* End of file EE_Session.class.php */
1081
/* Location: /includes/classes/EE_Session.class.php */
1082