Completed
Branch FET/11450/reserved-instance-in... (8a8133)
by
unknown
125:36 queued 112:25
created

EE_Session::reset_expired()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\domain\services\session\SessionIdentifierInterface;
4
use EventEspresso\core\domain\values\session\SessionLifespan;
5
use EventEspresso\core\exceptions\InvalidDataTypeException;
6
use EventEspresso\core\exceptions\InvalidInterfaceException;
7
use EventEspresso\core\exceptions\InvalidSessionDataException;
8
use EventEspresso\core\services\cache\CacheStorageInterface;
9
use EventEspresso\core\services\request\RequestInterface;
10
11
defined('EVENT_ESPRESSO_VERSION') || exit('NO direct script access allowed');
12
13
14
15
/**
16
 * EE_Session class
17
 *
18
 * @package    Event Espresso
19
 * @subpackage includes/classes
20
 * @author     Brent Christensen
21
 */
22
class EE_Session implements SessionIdentifierInterface
23
{
24
25
    const session_id_prefix    = 'ee_ssn_';
26
27
    const hash_check_prefix    = 'ee_shc_';
28
29
    const OPTION_NAME_SETTINGS = 'ee_session_settings';
30
31
    /**
32
     * instance of the EE_Session object
33
     *
34
     * @var EE_Session
35
     */
36
    private static $_instance;
37
38
    /**
39
     * @var CacheStorageInterface $cache_storage
40
     */
41
    protected $cache_storage;
42
43
    /**
44
     * EE_Encryption object
45
     *
46
     * @var EE_Encryption
47
     */
48
    protected $encryption;
49
50
    /**
51
     * the session id
52
     *
53
     * @var string
54
     */
55
    private $_sid;
56
57
    /**
58
     * session id salt
59
     *
60
     * @var string
61
     */
62
    private $_sid_salt;
63
64
    /**
65
     * session data
66
     *
67
     * @var array
68
     */
69
    private $_session_data = array();
70
71
    /**
72
     * how long an EE session lasts
73
     * default session lifespan of 1 hour (for not so instant IPNs)
74
     *
75
     * @var SessionLifespan $session_lifespan
76
     */
77
    private $session_lifespan;
78
79
    /**
80
     * session expiration time as Unix timestamp in GMT
81
     *
82
     * @var int
83
     */
84
    private $_expiration;
85
86
    /**
87
     * whether or not session has expired at some point
88
     *
89
     * @var boolean
90
     */
91
    private $_expired = false;
92
93
    /**
94
     * current time as Unix timestamp in GMT
95
     *
96
     * @var int
97
     */
98
    private $_time;
99
100
    /**
101
     * whether to encrypt session data
102
     *
103
     * @var bool
104
     */
105
    private $_use_encryption;
106
107
    /**
108
     * well... according to the server...
109
     *
110
     * @var null
111
     */
112
    private $_user_agent;
113
114
    /**
115
     * do you really trust the server ?
116
     *
117
     * @var null
118
     */
119
    private $_ip_address;
120
121
    /**
122
     * current WP user_id
123
     *
124
     * @var null
125
     */
126
    private $_wp_user_id;
127
128
    /**
129
     * array for defining default session vars
130
     *
131
     * @var array
132
     */
133
    private $_default_session_vars = array(
134
        'id'            => null,
135
        'user_id'       => null,
136
        'ip_address'    => null,
137
        'user_agent'    => null,
138
        'init_access'   => null,
139
        'last_access'   => null,
140
        'expiration'    => null,
141
        'pages_visited' => array(),
142
    );
143
144
    /**
145
     * timestamp for when last garbage collection cycle was performed
146
     *
147
     * @var int $_last_gc
148
     */
149
    private $_last_gc;
150
151
    /**
152
     * @var RequestInterface $request
153
     */
154
    protected $request;
155
156
157
    /**
158
     * @singleton method used to instantiate class object
159
     * @param CacheStorageInterface $cache_storage
160
     * @param SessionLifespan|null  $lifespan
161
     * @param RequestInterface      $request
162
     * @param EE_Encryption         $encryption
163
     * @return EE_Session
164
     * @throws InvalidArgumentException
165
     * @throws InvalidDataTypeException
166
     * @throws InvalidInterfaceException
167
     */
168
    public static function instance(
169
        CacheStorageInterface $cache_storage = null,
170
        SessionLifespan $lifespan = null,
171
        RequestInterface $request = null,
172
        EE_Encryption $encryption = null
173
    ) {
174
        // check if class object is instantiated
175
        // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via:
176
        // add_filter( 'FHEE_load_EE_Session', '__return_false' );
177
        if (! self::$_instance instanceof EE_Session && apply_filters('FHEE_load_EE_Session', true)) {
178
            self::$_instance = new self(
179
                $cache_storage,
0 ignored issues
show
Bug introduced by
It seems like $cache_storage defined by parameter $cache_storage on line 169 can be null; however, EE_Session::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
180
                $lifespan,
0 ignored issues
show
Bug introduced by
It seems like $lifespan defined by parameter $lifespan on line 170 can be null; however, EE_Session::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
181
                $request,
0 ignored issues
show
Bug introduced by
It seems like $request defined by parameter $request on line 171 can be null; however, EE_Session::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

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