Completed
Branch FET-8385-datetime-ticket-selec... (304b56)
by
unknown
51:42 queued 39:36
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
503
         if ( ! is_array($session_data)) {
504
             try {
505
	             $session_data = maybe_unserialize($session_data);
506
             } catch (Exception $e) {
507
                 $msg = esc_html__(
508
                     'An error occurred while attempting to unserialize the session data.',
509
                     'event_espresso'
510
                 );
511
                 $msg .= WP_DEBUG
512
                     ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
513
                     : '';
514
                 throw new InvalidSessionDataException($msg, 0, $e);
515
             }
516
         }
517
         // just a check to make sure the session array is indeed an array
518
         if ( ! is_array($session_data)) {
519
             // no?!?! then something is wrong
520
             $msg = esc_html__(
521
                 'The session data is missing, invalid, or corrupted.',
522
                 'event_espresso'
523
             );
524
             $msg .= WP_DEBUG
525
                 ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
526
                 : '';
527
	         throw new InvalidSessionDataException($msg);
528
         }
529
	     if ( isset( $this->_session_data['transaction'] ) && absint( $this->_session_data['transaction'] ) !== 0 ) {
530
		     $this->_session_data['transaction'] = EEM_Transaction::instance()->get_one_by_ID(
531
			     $this->_session_data['transaction']
532
	         );
533
	     }
534
	     return $session_data;
535
     }
536
537
538
539
	 /**
540
	  * _generate_session_id
541
	  * Retrieves the PHP session id either directly from the PHP session,
542
	  * or from the $_REQUEST array if it was passed in from an AJAX request.
543
	  * The session id is then salted and hashed (mmm sounds tasty)
544
	  * so that it can be safely used as a $_REQUEST param
545
	  *
546
	  * @return string
547
	  */
548
	protected function _generate_session_id() {
549
		// check if the SID was passed explicitly, otherwise get from session, then add salt and hash it to reduce length
550
		if ( isset( $_REQUEST[ 'EESID' ] ) ) {
551
			$session_id = sanitize_text_field( $_REQUEST[ 'EESID' ] );
552
		} else {
553
			$session_id = md5( session_id() . get_current_blog_id() . $this->_get_sid_salt() );
554
		}
555
		return apply_filters( 'FHEE__EE_Session___generate_session_id__session_id', $session_id );
556
	}
557
558
559
560
	 /**
561
	  * _get_sid_salt
562
	  *
563
	  * @return string
564
	  */
565
	protected function _get_sid_salt() {
566
		// was session id salt already saved to db ?
567
		if ( empty( $this->_sid_salt ) ) {
568
			// no?  then maybe use WP defined constant
569
			if ( defined( 'AUTH_SALT' ) ) {
570
				$this->_sid_salt = AUTH_SALT;
571
			}
572
			// if salt doesn't exist or is too short
573
			if ( empty( $this->_sid_salt ) || strlen( $this->_sid_salt ) < 32 ) {
574
				// create a new one
575
				$this->_sid_salt = wp_generate_password( 64 );
576
			}
577
			// and save it as a permanent session setting
578
			$session_settings = get_option( 'ee_session_settings' );
579
			$session_settings[ 'sid_salt' ] = $this->_sid_salt;
580
			update_option( 'ee_session_settings', $session_settings );
581
		}
582
		return $this->_sid_salt;
583
	}
584
585
586
587
	 /**
588
	  * _set_init_access_and_expiration
589
	  * @return void
590
	  */
591
	protected function _set_init_access_and_expiration() {
592
		$this->_time = time();
593
		$this->_expiration = $this->_time + $this->_lifespan;
594
		// set initial site access time
595
		$this->_session_data['init_access'] = $this->_time;
596
		// and the session expiration
597
		$this->_session_data['expiration'] = $this->_expiration;
598
	}
599
600
601
602
	 /**
603
	  * @update session data  prior to saving to the db
604
	  * @access public
605
	  * @param bool $new_session
606
	  * @return TRUE on success, FALSE on fail
607
	  */
608
	public function update( $new_session = FALSE ) {
609
		$this->_session_data = isset( $this->_session_data )
610
			&& is_array( $this->_session_data )
611
			&& isset( $this->_session_data['id'])
612
			? $this->_session_data
613
			: array();
614
		if ( empty( $this->_session_data )) {
615
			$this->_set_defaults();
616
		}
617
		$session_data = array();
618
		foreach ( $this->_session_data as $key => $value ) {
619
620
			switch( $key ) {
621
622
				case 'id' :
623
					// session ID
624
					$session_data['id'] = $this->_sid;
625
				break;
626
627
				case 'ip_address' :
628
					// visitor ip address
629
					$session_data['ip_address'] = $this->_visitor_ip();
630
				break;
631
632
				case 'user_agent' :
633
					// visitor user_agent
634
					$session_data['user_agent'] = $this->_user_agent;
635
				break;
636
637
				case 'init_access' :
638
					$session_data['init_access'] = absint( $value );
639
				break;
640
641
				case 'last_access' :
642
					// current access time
643
					$session_data['last_access'] = $this->_time;
644
				break;
645
646
				case 'expiration' :
647
					// when the session expires
648
					$session_data['expiration'] = ! empty( $this->_expiration )
649
						? $this->_expiration
650
						: $session_data['init_access'] + $this->_lifespan;
651
				break;
652
653
				case 'user_id' :
654
					// current user if logged in
655
					$session_data['user_id'] = $this->_wp_user_id();
656
				break;
657
658
				case 'pages_visited' :
659
					$page_visit = $this->_get_page_visit();
660
					if ( $page_visit ) {
661
						// set pages visited where the first will be the http referrer
662
						$this->_session_data[ 'pages_visited' ][ $this->_time ] = $page_visit;
663
						// we'll only save the last 10 page visits.
664
						$session_data[ 'pages_visited' ] = array_slice( $this->_session_data['pages_visited'], -10 );
665
					}
666
				break;
667
668
				default :
669
					// carry any other data over
670
					$session_data[$key] = $this->_session_data[$key];
671
672
			}
673
674
		}
675
676
		$this->_session_data = $session_data;
677
		// creating a new session does not require saving to the db just yet
678
		if ( ! $new_session ) {
679
			// ready? let's save
680
			if ( $this->_save_session_to_db() ) {
681
				return TRUE;
682
			} else {
683
				return FALSE;
684
			}
685
		}
686
		// meh, why not?
687
		return TRUE;
688
689
	}
690
691
692
693
	 /**
694
	  * since WordPress has no do_action()s within wp_safe_redirect,
695
	  * we have to hack into one of the supplied filters
696
	  * in order to make sure the session is updated prior to redirecting.
697
	  * This is a callback for the 'wp_redirect' filter
698
	  *
699
	  * @param string $location
700
	  * @return mixed
701
	  */
702
	 public function update_on_redirect( $location ) {
703
		 $this->update();
704
		 return $location;
705
	}
706
707
708
	/**
709
	 * 	@create session data array
710
	 * 	@access public
711
	 * 	@return bool
712
	 */
713
	private function _create_espresso_session( ) {
714
		do_action( 'AHEE_log', __CLASS__, __FUNCTION__, '' );
715
		// use the update function for now with $new_session arg set to TRUE
716
		return  $this->update( TRUE ) ? TRUE : FALSE;
717
	}
718
719
720
721
	 /**
722
	  * _save_session_to_db
723
	  *
724
	  * @access public
725
	  * @return string
726
	  * @throws \EE_Error
727
	  */
728
	private function _save_session_to_db() {
729
		if (
730
			// if the current request is NOT one of the following
731
			! (
732
				(
733
					// an espresso page
734
					EE_Registry::instance()->REQ instanceof EE_Request_Handler
735
					&& EE_Registry::instance()->REQ->is_espresso_page()
736
				)
737
				// OR an an AJAX request from the frontend
738
				|| EE_Registry::instance()->REQ->front_ajax
739
				// OR an admin request that is NOT AJAX
740
				|| (
741
					is_admin()
742
					&& ! ( defined( 'DOING_AJAX' ) && DOING_AJAX )
743
				)
744
			)
745
		) {
746
			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...
747
		}
748
		$transaction = $this->transaction();
749
		if ( $transaction instanceof EE_Transaction ) {
750
			if ( ! $transaction->ID() ) {
751
				$transaction->save();
752
			}
753
			$this->_session_data['transaction'] = $transaction->ID();
754
		}
755
		// then serialize all of our session data
756
		$session_data = serialize($this->_session_data);
757
		// do we need to also encode it to avoid corrupted data when saved to the db?
758
		$session_data = $this->_use_encryption ? $this->encryption->base64_string_encode( $session_data ) : $session_data;
759
		// maybe save hash check
760
		if ( apply_filters( 'FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG ) ) {
761
			set_transient( EE_Session::hash_check_prefix . $this->_sid, md5( $session_data ), $this->_lifespan );
762
		}
763
		// we're using the Transient API for storing session data, cuz it's so damn simple -> set_transient(  transient ID, data, expiry )
764
		return set_transient( EE_Session::session_id_prefix . $this->_sid, $session_data, $this->_lifespan );
765
	}
766
767
768
769
770
771
	/**
772
	 * _visitor_ip
773
	 *	attempt to get IP address of current visitor from server
774
	 * plz see: http://stackoverflow.com/a/2031935/1475279
775
	 *
776
	 *	@access public
777
	 *	@return string
778
	 */
779 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...
780
		$visitor_ip = '0.0.0.0';
781
		$server_keys = array(
782
			'HTTP_CLIENT_IP',
783
			'HTTP_X_FORWARDED_FOR',
784
			'HTTP_X_FORWARDED',
785
			'HTTP_X_CLUSTER_CLIENT_IP',
786
			'HTTP_FORWARDED_FOR',
787
			'HTTP_FORWARDED',
788
			'REMOTE_ADDR'
789
		);
790
		foreach ( $server_keys as $key ){
791
			if ( isset( $_SERVER[ $key ] )) {
792
				foreach ( array_map( 'trim', explode( ',', $_SERVER[ $key ] )) as $ip ) {
793
					if ( $ip === '127.0.0.1' || filter_var( $ip, FILTER_VALIDATE_IP ) !== FALSE ) {
794
						$visitor_ip = $ip;
795
					}
796
				}
797
			}
798
		}
799
		return $visitor_ip;
800
	}
801
802
803
804
805
806
	/**
807
	 *			@get the full page request the visitor is accessing
808
	 *		  	@access public
809
	 *			@return string
810
	 */
811
	public function _get_page_visit() {
812
		$page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
813
		// check for request url
814
		if ( isset( $_SERVER['REQUEST_URI'] )) {
815
			$http_host = '';
816
			$page_id = '?';
817
			$e_reg = '';
818
			$request_uri = esc_url( $_SERVER['REQUEST_URI'] );
819
			$ru_bits = explode( '?', $request_uri );
820
			$request_uri = $ru_bits[0];
821
			// check for and grab host as well
822
			if ( isset( $_SERVER['HTTP_HOST'] )) {
823
				$http_host = esc_url( $_SERVER['HTTP_HOST'] );
824
			}
825
			// check for page_id in SERVER REQUEST
826
			if ( isset( $_REQUEST['page_id'] )) {
827
				// rebuild $e_reg without any of the extra parameters
828
				$page_id = '?page_id=' . esc_attr( $_REQUEST['page_id'] ) . '&amp;';
829
			}
830
			// check for $e_reg in SERVER REQUEST
831
			if ( isset( $_REQUEST['ee'] )) {
832
				// rebuild $e_reg without any of the extra parameters
833
				$e_reg = 'ee=' . esc_attr( $_REQUEST['ee'] );
834
			}
835
			$page_visit = rtrim( $http_host . $request_uri . $page_id . $e_reg, '?' );
836
		}
837
		return $page_visit !== home_url( '/wp-admin/admin-ajax.php' ) ? $page_visit : '';
838
839
	}
840
841
842
843
844
845
	/**
846
	 * 	@the current wp user id
847
	 * 	@access public
848
	 * 	@return int
849
	 */
850
	public function _wp_user_id() {
851
		// if I need to explain the following lines of code, then you shouldn't be looking at this!
852
		$this->_wp_user_id = get_current_user_id();
853
		return $this->_wp_user_id;
854
	}
855
856
857
858
	 /**
859
	  * Clear EE_Session data
860
	  *
861
	  * @access public
862
	  * @param string $class
863
	  * @param string $function
864
	  * @return void
865
	  */
866
	public function clear_session( $class = '', $function = '' ) {
867
		//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>';
868
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' .  $function . '()' );
869
		$this->reset_cart();
870
		$this->reset_checkout();
871
		$this->reset_transaction();
872
		// wipe out everything that isn't a default session datum
873
		$this->reset_data( array_keys( $this->_session_data ));
874
		// reset initial site access time and the session expiration
875
		$this->_set_init_access_and_expiration();
876
		$this->_save_session_to_db();
877
	}
878
879
880
881
	 /**
882
	  * @resets all non-default session vars
883
	  * @access public
884
	  * @param array $data_to_reset
885
	  * @param bool  $show_all_notices
886
	  * @return TRUE on success, FALSE on fail
887
	  */
888
	public function reset_data( $data_to_reset = array(), $show_all_notices = FALSE ) {
889
		// if $data_to_reset is not in an array, then put it in one
890
		if ( ! is_array( $data_to_reset ) ) {
891
			$data_to_reset = array ( $data_to_reset );
892
		}
893
		// nothing ??? go home!
894
		if ( empty( $data_to_reset )) {
895
			EE_Error::add_error( __( 'No session data could be reset, because no session var name was provided.', 'event_espresso' ), __FILE__, __FUNCTION__, __LINE__ );
896
			return FALSE;
897
		}
898
		$return_value = TRUE;
899
		// since $data_to_reset is an array, cycle through the values
900
		foreach ( $data_to_reset as $reset ) {
901
902
			// first check to make sure it is a valid session var
903
			if ( isset( $this->_session_data[ $reset ] )) {
904
				// then check to make sure it is not a default var
905
				if ( ! array_key_exists( $reset, $this->_default_session_vars )) {
906
					// remove session var
907
					unset( $this->_session_data[ $reset ] );
908
					if ( $show_all_notices ) {
909
						EE_Error::add_success( sprintf( __( 'The session variable %s was removed.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
910
					}
911
					$return_value = !isset($return_value) ? TRUE : $return_value;
912
913 View Code Duplication
				} else {
914
					// yeeeeeeeeerrrrrrrrrrr OUT !!!!
915
					if ( $show_all_notices ) {
916
						EE_Error::add_error( sprintf( __( 'Sorry! %s is a default session datum and can not be reset.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
917
					}
918
					$return_value = FALSE;
919
				}
920
921 View Code Duplication
			} else if ( $show_all_notices ) {
922
				// oops! that session var does not exist!
923
				EE_Error::add_error( sprintf( __( 'The session item provided, %s, is invalid or does not exist.', 'event_espresso' ), $reset ), __FILE__, __FUNCTION__, __LINE__ );
924
				$return_value = FALSE;
925
			}
926
927
		} // end of foreach
928
929
		return $return_value;
930
931
	}
932
933
934
935
936
937
938
	/**
939
	 *   wp_loaded
940
	 *   @access public
941
	 */
942
	public function wp_loaded() {
943
		if ( isset(  EE_Registry::instance()->REQ ) && EE_Registry::instance()->REQ->is_set( 'clear_session' )) {
944
			$this->clear_session( __CLASS__, __FUNCTION__ );
945
		}
946
	}
947
948
949
950
	/**
951
	 * Used to reset the entire object (for tests).
952
	 *
953
	 * @since 4.3.0
954
	 *
955
	 */
956
	public function reset_instance() {
957
		$this->clear_session();
958
		self::$_instance = NULL;
959
	}
960
961
962
963
	 /**
964
	  * garbage_collection
965
	  * @since 4.3.0
966
	  */
967
	 public function garbage_collection() {
968
		 // only perform during regular requests
969
		 if ( ! defined( 'DOING_AJAX') || ! DOING_AJAX ) {
970
			 /** @type WPDB $wpdb */
971
			 global $wpdb;
972
			 // since transient expiration timestamps are set in the future, we can compare against NOW
973
			 $expiration = time();
974
			 $too_far_in_the_the_future = $expiration + ( $this->_lifespan * 2 );
975
			 // filter the query limit. Set to 0 to turn off garbage collection
976
			 $expired_session_transient_delete_query_limit = absint( apply_filters( 'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit', 50 ));
977
			 // non-zero LIMIT means take out the trash
978
			 if ( $expired_session_transient_delete_query_limit ) {
979
				 //array of transient keys that require garbage collection
980
				 $session_keys = array(
981
					 EE_Session::session_id_prefix,
982
					 EE_Session::hash_check_prefix,
983
				 );
984
				 foreach ( $session_keys as $session_key ) {
985
					 $session_key = str_replace( '_', '\_', $session_key );
986
					 $session_key = '\_transient\_timeout\_' . $session_key . '%';
987
					 $SQL = "
988
					SELECT option_name
989
					FROM {$wpdb->options}
990
					WHERE option_name
991
					LIKE '{$session_key}'
992
					AND ( option_value < {$expiration}
993
					OR option_value > {$too_far_in_the_the_future} )
994
					LIMIT {$expired_session_transient_delete_query_limit}
995
				";
996
					 $expired_sessions = $wpdb->get_col( $SQL );
997
					 // valid results?
998
					 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...
999
						 // format array of results into something usable within the actual DELETE query's IN clause
1000
						 $expired = array();
1001
						 foreach ( $expired_sessions as $expired_session ) {
1002
							 $expired[ ] = "'" . $expired_session . "'";
1003
							 $expired[ ] = "'" . str_replace( 'timeout_', '', $expired_session ) . "'";
1004
						 }
1005
						 $expired = implode( ', ', $expired );
1006
						 $SQL = "
1007
						DELETE FROM {$wpdb->options}
1008
						WHERE option_name
1009
						IN ( $expired );
1010
					 ";
1011
						 $results = $wpdb->query( $SQL );
1012
						 // if something went wrong, then notify the admin
1013
						 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...
1014
							 EE_Error::add_error( $results->get_error_message(), __FILE__, __FUNCTION__, __LINE__ );
1015
						 }
1016
					 }
1017
				 }
1018
				 do_action(
1019
					 'FHEE__EE_Session__garbage_collection___end',
1020
					 $expired_session_transient_delete_query_limit
1021
				 );
1022
			 }
1023
		 }
1024
1025
1026
	 }
1027
1028
1029
1030
	 /**
1031
	  * @see http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset/21389439#10152996
1032
	  * @param $data1
1033
	  * @return string
1034
	  */
1035
	 private function find_serialize_error( $data1 ) {
1036
		$error = "<pre>";
1037
		 $data2 = preg_replace_callback(
1038
			 '!s:(\d+):"(.*?)";!',
1039 View Code Duplication
			 function ( $match ) {
1040
				 return ( $match[1] === strlen( $match[2] ) )
1041
					 ? $match[0]
1042
					 : 's:'
1043
					   . strlen( $match[2] )
1044
					   . ':"'
1045
					   . $match[2]
1046
					   . '";';
1047
			 },
1048
			 $data1
1049
		 );
1050
		$max = ( strlen( $data1 ) > strlen( $data2 ) ) ? strlen( $data1 ) : strlen( $data2 );
1051
		$error .= $data1 . PHP_EOL;
1052
		$error .= $data2 . PHP_EOL;
1053
		for ( $i = 0; $i < $max; $i++ ) {
1054
			if ( @$data1[ $i ] !== @$data2[ $i ] ) {
1055
				$error .= "Difference " . @$data1[ $i ] . " != " . @$data2[ $i ] . PHP_EOL;
1056
				$error .= "\t-> ORD number " . ord( @$data1[ $i ] ) . " != " . ord( @$data2[ $i ] ) . PHP_EOL;
1057
				$error .= "\t-> Line Number = $i" . PHP_EOL;
1058
				$start = ( $i - 20 );
1059
				$start = ( $start < 0 ) ? 0 : $start;
1060
				$length = 40;
1061
				$point = $max - $i;
1062
				if ( $point < 20 ) {
1063
					$rlength = 1;
1064
					$rpoint = -$point;
1065
				} else {
1066
					$rpoint = $length - 20;
1067
					$rlength = 1;
1068
				}
1069
				$error .= "\t-> Section Data1  = ";
1070
				$error .= substr_replace(
1071
					substr( $data1, $start, $length ),
1072
					"<b style=\"color:green\">{$data1[ $i ]}</b>",
1073
					$rpoint,
1074
					$rlength
1075
				);
1076
				$error .= PHP_EOL;
1077
				$error .= "\t-> Section Data2  = ";
1078
				$error .= substr_replace(
1079
					substr( $data2, $start, $length ),
1080
					"<b style=\"color:red\">{$data2[ $i ]}</b>",
1081
					$rpoint,
1082
					$rlength
1083
				);
1084
				$error .= PHP_EOL;
1085
			}
1086
		}
1087
		$error .= "</pre>";
1088
		return $error;
1089
	}
1090
1091
 }
1092
/* End of file EE_Session.class.php */
1093
/* Location: /includes/classes/EE_Session.class.php */
1094